如何处理中继突变中未解决的道具?

How to handle unresolved props in relay mutations?

我创建了一个 Relay.Mutation 应该触发 User 对象的更新:

class UserMutation extends Relay.Mutation {
    public getMutation() {
        return Relay.QL`mutation {saveUser}`;
    }

    public getVariables() {
        return {
            id: this.props.user.id,
            loginName: this.props.user.loginName,
            firstName: this.props.user.firstName,
            lastName: this.props.user.lastName,
            mail: this.props.user.mail
        }
    }

    public getFatQuery() {
        return Relay.QL`
            fragment on UserPayload {
                user {
                    id,
                    loginName,
                    firstName,
                    lastName,
                    mail
                }
            }
        `;
    }

    public getConfigs() {
        return [{
            type: "FIELDS_CHANGE",
            fieldIDs: {
                user: this.props.user.id
            }
        }];
    }

    static fragments = {
        user: () => Relay.QL`
            fragment on User {
                id,
                // I initially had only id here
                loginName,
                firstName,
                lastName,
                mail
            }
        `
    }
}

我在我的组件 UserDetails 中使用这个突变,如下所示:

// I initially passed this.props.user to the mutation
this.props.relay.commitUpdate(new UserMutation({ user: this.state.user })

执行时,中继将 user 传递给后端,仅设置 id,没有任何其他 属性。未执行突变,因为输入变量缺少其他字段。

调试突变后,我看到 this.props.user 已在除 id 之外的所有字段上设置 undefined。但是,this._unresolvedProps.user 是一个 user,所有字段都设置正确。

当我更改突变的代码并将所有 this.props 替换为 this._unresolvedProps 时,所有必要的数据都被传输到后端并且突变执行时没有任何错误。前端缓存似乎也已正确更新(firstName 等字段在其他组件中已更新)。但我不认为这是正确的方法。

我错过了什么?

更新

UserDetails 组件像 loginName 一样加载用户数据并提供文本框来更改这些属性。对应的中继容器如下所示:

export default Relay.createContainer(UserDetails, {
    fragments: {
        user: () => Relay.QL`
            fragment on User {
                id,
                loginName,
                firstName,
                lastName,
                mail,
                roles {
                    id,
                    name
                },
                ${UserMutation.getFragment("user")}
            }
        `
    }
});

我在文本输入处理程序中处理文本框更改...

public handleTextInput(fieldName: string, event: any) {
    let user = this.state.user;

    switch (fieldName) {
        case "loginName": {
            user.loginName = event.target.value;
            break;
        }
        case "firstName": {
            user.firstName = event.target.value;
            break;
        }
        case "lastName": {
            user.lastName = event.target.value;
            break;
        }
        case "mail": {
            user.mail = event.target.value;
            break;
        }
    }

    this.setState({ user: user });
}

...并在提交处理程序中提交表单,我现在将 this.state.user 传递给突变:

public handleSubmit(e: any) {
    e.preventDefault();
    this.props.relay.commitUpdate(new UserMutation({ user: this.state.user }), {
        onSuccess: (response: any) => {
            this.setState({ user: response.saveUser.user });
        }
    });
}

我使用 C# 后端:graphql-dotnet。这是我为突变定义的:

public class ApplicationSchema : Schema
{
    public ApplicationSchema()
    {
        this.Query = new ApplicationQuery();
        this.Mutation = new ApplicationMutation();
    }
}

public class ApplicationMutation : ObjectGraphType
{
    public ApplicationMutation()
    {
        this.Name = "Mutation";

        // save a user
        this.Field<UserPayloadType>(
            "saveUser",
             arguments: new QueryArguments(
             new QueryArgument<NonNullGraphType<UserInputType>>
             {
                 Name = "input",
                 Description = "the user that should be saved"
             }),
            resolve: context =>
                {
                    var userInput = context.Argument<UserInput>("input");
                    var clientMutationId = userInput.ClientMutationId;

                    var user = MemoryRepository.UpdateUser(new User()
                    {
                        Id = userInput.Id,
                        LoginName = userInput.LoginName,
                        FirstName = userInput.FirstName,
                        LastName = userInput.LastName,
                        Mail = userInput.Mail
                    });

                    return new UserPayload()
                    {
                        ClientMutationId = clientMutationId,
                        User = user
                    };
                });
    }
}

public class UserInputType : InputObjectGraphType
{
    public UserInputType()
    {
        this.Name = "UserInput";

        this.Field<NonNullGraphType<StringGraphType>>("id", "The id of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("loginName", "The login name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("firstName", "The first name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("lastName", "The last name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("mail", "The mail adress of the user.");

        this.Field<NonNullGraphType<StringGraphType>>("clientMutationId", "react-relay property.");
    }
}

public class UserPayloadType : ObjectGraphType
{
    public UserPayloadType()
    {
        this.Name = "UserPayload";

        this.Field<NonNullGraphType<UserType>>("user", "The user.");

        this.Field<NonNullGraphType<StringGraphType>>("clientMutationId", "react-relay property.");
    }
}

public class UserType : ObjectGraphType
{
    public UserType()
    {
        this.Name = "User";
        this.Field<NonNullGraphType<StringGraphType>>("id", "The id of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("loginName", "The login name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("firstName", "The first name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("lastName", "The last name of the user.");
        this.Field<NonNullGraphType<StringGraphType>>("mail", "The mail adress of the user.");

        Field<ListGraphType<RoleType>>("roles", resolve: context => MemoryRepository.GetRolesOfUser(context.Source as DomainModel.Models.User));
    }
}

使用REQUIRED_CHILDREN并更新组件中的状态。

您可以使用 REQUIRED_CHILDREN 而不是使用 FIELDS_CHANGE,这样您就可以将返回的已保存对象添加到您的商店中。您要做的是像这样设置您的 getConfigs:

getConfigs() {
  return [{
    type: 'REQUIRED_CHILDREN',
      children: [
        Relay.QL`
          fragment on UserPayload {
            user {
              id
              loginName
              firstName
              lastName
              mail
            }
          }
        `
      ]
  }]
}

然后像这样更改您的 commitUpdate:

this.props.relay.commitUpdate(
  new UserMutation({user: this.props.user}),
  {
    onSuccess: response => this.setState({
      user: response.user,
    }),
    onError: err => console.log(err)
  }
);

如您所见,onSuccess 回调使您能够调用 actionCreator 并将新用户置于您的应用程序状态。您可以使用您在应用程序中使用的任何状态管理来做到这一点。在这种情况下,它只是 setState.

REQUIRED_CHILDREN config is used to append additional children to the mutation query. You may need to use this, for example, to fetch fields on a new object created by the mutation (and which Relay would normally not attempt to fetch because it has not previously fetched anything for that object).

Data fetched as a result of a REQUIRED_CHILDREN config is not written into the client store, but you can add code that processes it in the onSuccess callback that you pass into commitUpdate()

There is more information in the documentation about REQUIRED_CHILDREN here.

您的中继容器是否正确获取了 User 片段?我在您的 static fragments 用户定义片段中看到只有 id 字段,所以我想知道您的父 Relay 组件是否正在获取它们。

由于您的突变确实依赖于这些字段,因此将它们添加到 fragments 属性.

class UserMutation extends Relay.Mutation {
    public getMutation() { ... }

    // getVariables, FatQuery and Configs ...

    static fragments = {
      user: () => Relay.QL`
          fragment on User {
              id,
              loginName,
              firstName,
              lastName,
              mail
          }
      `
    }
}

然后尝试将此片段包含在 Relay 组件中,它使用了您的变更。 React-Relay 组件示例:

import UserMutation from 'mutations/user';

class User extends Component {
  commit(e) {
    Relay.Store.commitUpdate(
      new UserMutation({
        user: this.props.user
      })
    );
  }

  render() {
    return (
      <div>Hello</div>
    );
  }
};

export default Relay.createContainer(User, {
  fragments: {
    user: () => Relay.QL`
      fragment on User {
        ${UserMutation.getFragment('user')}
      }
    `,
  }
});