什么类型的 Relay mutator 配置适合插入新记录?
What type of Relay mutator configuration is appropriate for inserting a new record?
我正在研究 GraphQL 和 Relay。到目前为止,对我来说,一切都相对顺利和容易理解。我有一个使用 Accounts
和 Teams
的 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 命名突变 - 对你来说,那是
AddAccountMutation
- 根据受突变影响的内容定义胖查询 - 对您来说,它只是
viewer
上的 accounts
连接,但将来我相信您的模式会变得更多复杂
- 根据如何将胖查询与本地图相交来定义变异配置
- 定义满足#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_DELETE
、RANGE_ADD
等
我正在研究 GraphQL 和 Relay。到目前为止,对我来说,一切都相对顺利和容易理解。我有一个使用 Accounts
和 Teams
的 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 命名突变 - 对你来说,那是
AddAccountMutation
- 根据受突变影响的内容定义胖查询 - 对您来说,它只是
viewer
上的accounts
连接,但将来我相信您的模式会变得更多复杂 - 根据如何将胖查询与本地图相交来定义变异配置
- 定义满足#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_DELETE
、RANGE_ADD
等