【问题标题】:What type of Relay mutator configuration is appropriate for inserting a new record?什么类型的 Relay mutator 配置适合插入新记录?
【发布时间】:2016-04-03 23:43:21
【问题描述】:

我正在深入研究 GraphQL 和 Relay。到目前为止,对我来说,一切都比较顺利和容易理解。我有一个与 AccountsTeams 一起使用的 GraphQL 模式。两者之间还没有任何关系。我对帐户和团队的连接进行了一些特定于 Relay 的 GraphQL 调整。这是这两个连接的示例查询...

{
  viewer {
    teams {
      edges {
        node {
          id,
          name
        }
      }
    }
    accounts {
      edges {
        node {
          id,
          username
        }
      }
    }
  }
}

我已经准备好创建一个新帐户的 GraphQL 突变。这是 GraphQL 的表示形式...

type Mutation {
  newAccount(input: NewAccountInput!): NewAccountPayload
}

input NewAccountInput {
  username: String!
  password: String!
  clientMutationId: String!
}

type NewAccountPayload {
  account: Account
  clientMutationId: String!
}

type Account implements Node {
  id: ID!
  username: String!
  date_created: String!
}

我现在正在尝试创建使用此 GraphQL 突变的客户端 Relay 突变。不过,我对如何正确执行此操作感到非常困惑。我已经按照示例进行了操作,但我想出的任何东西似乎都无法正常运行。我往往会遇到与片段组合有关的错误。

如果我正在编写一个使用此 GraphQL 突变的 Relay 突变,那么合适的突变器配置应该是什么?我应该使用RANGE_ADD吗?

【问题讨论】:

    标签: javascript relayjs graphql


    【解决方案1】:

    对于您的客户端突变,您可以使用以下内容:

    export default class AddAccountMutation extends Relay.Mutation {
       static fragments = {
          viewer: () => Relay.QL`
             fragment on Viewer {
                id,
             }
           `,
       };
    
       getMutation() {
          return Relay.QL`mutation{addAccount}`;
       }
    
       getVariables() {
          return {
             newAccount: this.props.newAccount,
          };
       }
    
       getFatQuery() {
          return Relay.QL`
             fragment on AddAccountPayload {
                accountEdge,
                viewer {
                   accounts,
                },
             }
           `;
        }
    
       getConfigs() {
          return [{
             type: 'RANGE_ADD',
             parentName: 'viewer',
             parentID: this.props.viewer.id,
             connectionName: 'accounts',
             edgeName: 'accountEdge',
             rangeBehaviors: {
                '': 'append',
             },
          }];
       }
    
       getOptimisticResponse() {
          return {
             accountEdge: {
                node: {
                   userName: this.props.newAccount.userName,
                },
             },
             viewer: {
                id: this.props.viewer.id,
             },
          };
       }
    }
    

    然后,在您的 GraphQL 架构中,您需要返回新创建的边以及光标:

    var GraphQLAddAccountMutation = mutationWithClientMutationId({
       name: 'AddAccount',
       inputFields: {
          newAccount: { type: new GraphQLNonNull(NewAccountInput) } 
       },
       outputFields: {
          accountEdge: {
             type: GraphQLAccountEdge,
             resolve: async ({localAccountId}) => {
                var account = await getAccountById(localAccountId);
                var accounts = await getAccounts();
                return {
                  cursor: cursorForObjectInConnection(accounts, account)
                  node: account,
                };
             }
          },
          viewer: {
            type: GraphQLViewer,
            resolve: () => getViewer()
          },
       },
       mutateAndGetPayload: async ({ newAccount }) => {
          var localAccountId = await createAccount(newAccount);
          return {localAccountId};
       }
    });
    
    var {
       connectionType: AccountsConnection,
       edgeType: GraphQLAccountEdge,
    } = connectionDefinitions({
       name: 'Account',
       nodeType: Account,
    });
    

    您需要将 getAccounts()、getAccountById() 和 createAccount 方法调用替换为您的服务器/后端使用的任何方法。

    可能有更好的方法来计算光标,而无需执行多次服务器行程,但请记住中继助手 cursorForObjectInConnection 不会对对象进行任何类型的深度比较,因此如果您需要通过以下方式查找帐户列表中的 id,您可能需要进行自定义比较:

    function getCursor(dataList, item) {
       for (const i of dataList) {
          if (i._id.toString() === item._id.toString()) {
             let cursor = cursorForObjectInConnection(dataList, i);
             return cursor;
          } 
       }
    }
    

    最后,将 GraphQL 突变作为“addAccount”添加到您的架构突变字段中,该字段由客户端突变引用。

    【讨论】:

    • 几个问题,因为这看起来与我在自己的代码中所做的非常接近... 1. 为什么在输出字段中提供viewer 字段?你怎么知道要这样做? 2. 在您的突变片段中,为什么片段似乎与viewer 模式不匹配,我假设它包含一些连接,例如query { viewer { accounts { edges { node { id, username, ... }}}}}?我在你的胖查询中看到了这个结构。
    • 关于您的问题: 1. 我认为查看器应该包含在输出字段中(并因此通过胖查询返回给客户端的原因是查看器包含帐户列表,并且因此应该在插入新节点后获取。FB 在他们的 ToDo App 示例中包含此内容(在 AddTodoMutation 中)。2.片段应包含突变本身所需的任何字段,例如 getOptimisticResponse。因为帐户是从服务器通过胖查询,我们真的不需要在片段模式中指定“帐户”。
    【解决方案2】:

    现在,我正在按照大约 5 个步骤来定义突变:

    1. 根据您所定位的图表部分定义输入变量 - 在您的情况下,它是一个新帐户,因此您只需要新数据
    2. 根据 #1 命名突变 - 对你来说,那就是 AddAccountMutation
    3. 根据受突变影响的内容定义胖查询 - 对您而言,这只是 viewer 上的 accounts 连接,但我相信您的架构将来会变得更加复杂
    4. 根据如何将胖查询与本地图相交来定义突变配置
    5. 定义您需要满足#1、#3 和#4 要求的突变片段

    一般来说,第 4 步是人们最困惑的一步。那是因为它令人困惑。很难在 Stack Overflow 的回答中总结为什么我觉得这是个好建议,但是……我建议您使用 FIELDS_CHANGE 来处理所有突变*。这相对容易解释和推理 - 只需告诉 Relay 如何查找与突变有效负载中的顶级字段相对应的节点。然后,Relay 将使用突变配置来构建一个“跟踪查询”,代表您到目前为止所请求的所有内容,并将其与代表所有可能更改的“胖查询”相交。在您的情况下,您希望相交查询类似于viewer { accounts(first: 10) { edges { nodes { ... } } },这意味着您需要确保您已经在某处请求了现有帐户。但是你几乎肯定有,如果你没有……也许你实际上不需要在本地为这个突变做任何改变!

    有意义吗?

    编辑:为了清楚起见,这就是我对胖查询和配置的意思。

    getConfigs() {
      return [
      {
        type: "FIELDS_CHANGE",
        fieldIDs: {
          viewer: this.props.viewer.id
        }
      }]
    }
    
    getFatQuery() {
      return Relay.QL`
        fragment on AddAccountMutation {
          viewer {
            accounts
          }
        }
      `
    }
    

    *附录:我目前认为只有一两个理由不使用FIELDS_CHANGE。首先是您无法可靠地说明哪些字段正在发生变化,因此您只想直接操作图表。第二个是因为您决定需要FIELDS_CHANGE 的更具体变体(如NODE_DELETERANGE_ADD 等)提供的查询性能优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-14
      相关资源
      最近更新 更多