什么类型的 Relay mutator 配置适合插入新记录?

What type of Relay mutator configuration is appropriate for inserting a new record?

我正在研究 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 变更的客户端中继变更。不过,我对如何正确执行此操作感到非常困惑。我遵循了示例,但我想出的任何东西似乎 运行 都不正确。我往往会收到与片段组合相关的错误。

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

对于你的客户端变更,你可以使用这样的东西:

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 架构中,您需要 return 新创建的边以及游标:

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 方法调用替换为您的 server/back-end 使用的任何内容。

可能有更好的方法来计算游标而不必进行多次服务器访问,但请记住,中继助手 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' 添加到您的架构变更字段,客户端变更将引用该字段。

现在,我正在按照大致 5 步的过程来定义突变:

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

一般来说,第 4 步是人们最容易混淆的一步。那是因为它令人困惑。很难在 Stack Overflow 的回答中总结为什么我觉得这是个好建议,但是...我建议您对所有突变*使用 FIELDS_CHANGE。这相对容易解释和推理 - 只需告诉 Relay 如何查找与突变有效负载中的顶级字段对应的节点。 Relay 然后将使用变异配置来构建一个 "tracked query" 来代表您到目前为止所请求的所有内容,并将其与代表所有可能更改的 "fat query" 相交。在您的情况下,您希望相交查询类似于 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