更新连接
通常,当您渲染连接时,您还需要能够根据用户操作向连接添加或删除项目。
正如我们 更新数据 部分所解释的,Relay 维护着归一化 GraphQL 数据的本地内存存储,其中记录按其 ID 存储。在使用 Relay 创建变异、订阅或本地数据更新时,您必须提供一个 updater
函数,您可以在其中访问和读取记录,以及写入和更新它们。当记录更新时,任何受更新数据影响的组件都将被通知并重新渲染。
连接记录
在 Relay 中,标记有 @connection
指令的连接字段作为存储中的特殊记录存储,它们保存并累积迄今为止为连接获取的所有项目。为了向连接添加或删除项目,我们需要使用连接 key
访问连接记录,该连接 key
在声明 @connection
时提供;具体来说,这允许我们使用 ConnectionHandler
API 在 updater
函数中访问连接。
例如,给定以下声明 @connection
的片段,我们可以通过几种不同的方式在 updater
函数中访问连接记录
const {graphql} = require('react-relay');
const storyFragment = graphql`
fragment StoryComponent_story on Story {
comments @connection(key: "StoryComponent_story_comments_connection") {
nodes {
body {
text
}
}
}
}
`;
使用 __id
访问连接
我们可以查询连接的 __id
字段,然后使用该 __id
在存储中访问记录
const fragmentData = useFragment(
graphql`
fragment StoryComponent_story on Story {
comments @connection(key: "StoryComponent_story_comments_connection") {
# Query for the __id field
__id
# ...
}
}
`,
props.story,
);
// Get the connection record id
const connectionID = fragmentData?.comments?.__id;
然后使用它在存储中访问记录
function updater(store: RecordSourceSelectorProxy) {
// connectionID is passed as input to the mutation/subscription
const connection = store.get(connectionID);
// ...
}
__id
字段不是您的 GraphQL API 需要公开的内容。相反,它是一个 Relay 自动添加的标识符,用于标识连接记录。
使用 ConnectionHandler.getConnectionID
访问连接
如果我们能够访问包含连接的父记录的 ID,我们可以使用 ConnectionHandler.getConnectionID
API 访问连接记录
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
// Get the connection ID
const connectionID = ConnectionHandler.getConnectionID(
storyID, // passed as input to the mutation/subscription
'StoryComponent_story_comments_connection',
);
// Get the connection record
const connectionRecord = store.get(connectionID);
// ...
}
使用 ConnectionHandler.getConnection
访问连接
如果我们能够访问包含连接的父记录,我们可以通过父级访问连接记录,方法是使用 ConnectionHandler.getConnection
API
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
// Get parent story record
// storyID is passed as input to the mutation/subscription
const storyRecord = store.get(storyID);
// Get the connection record from the parent
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
);
// ...
}
添加边
有几种方法可以向连接添加边
使用声明性指令
通常,变异或订阅有效负载将公开服务器上添加的新边,作为具有单个边或边列表的字段。如果您的变异或订阅公开了一个可以在响应中查询的边或边字段,那么您可以使用 @appendEdge
和 @prependEdge
声明性变异指令来将新创建的边添加到指定的连接(请注意,这些指令也适用于查询)。
或者,变异或订阅有效负载可能会公开服务器上添加的新节点,作为具有单个节点或节点列表的字段。如果您的变异或订阅公开了一个可以在响应中查询的节点或节点字段,那么您可以使用 @appendNode
和 @prependNode
声明性变异指令来添加新创建的节点,这些节点封装在边中,到指定的连接(请注意,这些指令也适用于查询)。
这些指令接受一个 connections
参数,该参数需要是一个包含连接 ID 数组的 GraphQL 变量。连接 ID 可以通过使用 连接上的 __id
字段 或使用 ConnectionHandler.getConnectionID
API 获取。
@appendEdge
/ @prependEdge
这些指令适用于具有单个边或边列表的字段。@prependEdge
将将选定的边添加到 connections
数组中定义的每个连接的开头,而 @appendEdge
将将选定的边添加到数组中每个连接的末尾。
参数
connections
:连接 ID 数组。连接 ID 可以通过使用 连接上的__id
字段 或使用ConnectionHandler.getConnectionID
API 获取。
示例
// Get the connection ID using the `__id` field
const connectionID = fragmentData?.comments?.__id;
// Or get it using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID(
'<story-id>',
'StoryComponent_story_comments_connection',
);
// ...
// Mutation
commitMutation<AppendCommentMutation>(environment, {
mutation: graphql`
mutation AppendCommentMutation(
# Define a GraphQL variable for the connections array
$connections: [ID!]!
$input: CommentCreateInput
) {
commentCreate(input: $input) {
# Use @appendEdge or @prependEdge on the edge field
feedbackCommentEdge @appendEdge(connections: $connections) {
cursor
node {
id
}
}
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
@appendNode
/ @prependNode
这些指令适用于具有单个节点或节点列表的字段,并将创建具有指定 edgeTypeName
的边。@prependNode
将在 connections
数组中定义的每个连接的开头添加包含选定节点的边,而 @appendNode
将在数组中每个连接的末尾添加包含选定节点的边。
参数
connections
:连接 ID 数组。连接 ID 可以通过使用 连接上的__id
字段 或使用ConnectionHandler.getConnectionID
API 获取。edgeTypeName
:包含节点的边的类型名称,对应于ConnectionHandler.createEdge
中的边类型参数。
示例
// Get the connection ID using the `__id` field
const connectionID = fragmentData?.comments?.__id;
// Or get it using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID(
'<story-id>',
'StoryComponent_story_comments_connection',
);
// ...
// Mutation
commitMutation<AppendCommentMutation>(environment, {
mutation: graphql`
mutation AppendCommentMutation(
# Define a GraphQL variable for the connections array
$connections: [ID!]!
$input: CommentCreateInput
) {
commentCreate(input: $input) {
# Use @appendNode or @prependNode on the node field
feedbackCommentNode @appendNode(connections: $connections, edgeTypeName: "CommentsEdge") {
id
}
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
执行顺序
对于所有这些指令,它们将在变异或订阅中按以下顺序执行,按照 更新程序函数的执行顺序
- 当变异启动时,在处理乐观响应后,以及在执行乐观更新程序函数后,将应用
@prependEdge
、@appendEdge
、@prependNode
和@appendNode
指令。 - 如果变异成功,在将来自网络响应的数据与存储中的现有值合并后,以及在执行更新程序函数后,将应用
@prependEdge
、@appendEdge
、@prependNode
和@appendNode
指令。 - 如果变异失败,处理
@prependEdge
、@appendEdge
、@prependNode
和@appendNode
指令的更新将被回滚。
手动添加边
上面描述的指令在很大程度上消除了手动添加和删除连接项目的必要性,但是,它们提供的控制程度不如手动编写更新程序,并且可能无法满足所有用例。
为了编写更新程序以修改连接,我们需要确保能够访问 连接记录。一旦我们有了连接记录,我们还需要一个要添加到连接的新边的记录。通常,变异或订阅有效负载将包含添加的新边;如果没有,您也可以从头开始构建一个新边。
例如,在以下变异中,我们可以在变异响应中查询新创建的边
const {graphql} = require('react-relay');
const createCommentMutation = graphql`
mutation CreateCommentMutation($input: CommentCreateData!) {
comment_create(input: $input) {
comment_edge {
cursor
node {
body {
text
}
}
}
}
}
`;
- 请注意,我们还查询了新边的
cursor
;这并非严格必要,但它是我们需要根据该cursor
执行分页时所需的信息。
在 updater
内部,我们可以使用 Relay 存储 API 访问变异响应中的边
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
);
// Get the payload returned from the server
const payload = store.getRootField('comment_create');
// Get the edge inside the payload
const serverEdge = payload.getLinkedRecord('comment_edge');
// Build edge for adding to the connection
const newEdge = ConnectionHandler.buildConnectionEdge(
store,
connectionRecord,
serverEdge,
);
// ...
}
- 变异有效负载作为该存储的根字段可用,可以使用
store.getRootField
API 读取。在本例中,我们正在读取comment_create
,它是响应中的根字段。 - 请注意,我们需要使用
ConnectionHandler.buildConnectionEdge
从服务器接收到的边构建新边,然后才能将其添加到连接中。
如果您需要从头开始创建一个新边,可以使用 ConnectionHandler.createEdge
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
);
// Create a new local Comment record
const id = `client:new_comment:${randomID()}`;
const newCommentRecord = store.create(id, 'Comment');
// Create new edge
const newEdge = ConnectionHandler.createEdge(
store,
connectionRecord,
newCommentRecord,
'CommentEdge', /* GraphQl Type for edge */
);
// ...
}
一旦我们有了新的边记录,我们就可以使用 ConnectionHandler.insertEdgeAfter
或 ConnectionHandler.insertEdgeBefore
将其添加到连接中
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
);
const newEdge = (...);
// Add edge to the end of the connection
ConnectionHandler.insertEdgeAfter(
connectionRecord,
newEdge,
);
// Add edge to the beginning of the connection
ConnectionHandler.insertEdgeBefore(
connectionRecord,
newEdge,
);
}
- 请注意,这些 API 将就地修改连接
查看我们完整的 Relay 存储 API。
删除边
使用声明性删除指令
与添加边的指令类似,我们可以使用 `@deleteEdge` 指令从连接中删除边。如果您的变异或订阅公开了包含已删除节点 ID 或 ID 的字段,您可以在响应中查询这些字段,那么您可以在该字段上使用 `@deleteEdge` 指令从连接中删除相应的边(请注意,此指令也适用于查询)。
@deleteEdge
适用于返回 `ID` 或 `[ID]` 的 GraphQL 字段。将删除与 `connections` 数组中定义的每个连接的 `id` 匹配的节点的边。
参数
connections
:连接 ID 数组。连接 ID 可以通过使用 连接上的__id
字段 或使用ConnectionHandler.getConnectionID
API 获取。
示例
// Get the connection ID using the `__id` field
const connectionID = fragmentData?.comments?.__id;
// Or get it using `ConnectionHandler.getConnectionID()`
const connectionID = ConnectionHandler.getConnectionID(
'<story-id>',
'StoryComponent_story_comments_connection',
);
// ...
// Mutation
commitMutation<DeleteCommentsMutation>(environment, {
mutation: graphql`
mutation DeleteCommentsMutation(
# Define a GraphQL variable for the connections array
$connections: [ID!]!
$input: CommentsDeleteInput
) {
commentsDelete(input: $input) {
deletedCommentIds @deleteEdge(connections: $connections)
}
}
`,
variables: {
input,
// Pass the `connections` array
connections: [connectionID],
},
});
手动删除边
ConnectionHandler
提供类似的 API,通过 `ConnectionHandler.deleteNode` 从连接中删除边。
const {ConnectionHandler} = require('RelayModern');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
const connectionRecord = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
);
// Remove edge from the connection, given the ID of the node
ConnectionHandler.deleteNode(
connectionRecord,
commentIDToDelete,
);
}
- 在这种情况下,`ConnectionHandler.deleteNode` 将根据
node
ID 删除边。这意味着它将查找连接中包含具有提供 ID 的节点的边,并删除该边。 - 请注意,此 API 将就地修改连接。
请记住:当执行此处描述的任何操作来修改连接时,渲染受影响连接的任何片段或查询组件都将收到通知并使用连接的最新版本重新渲染。
带过滤器的连接标识
在我们之前的示例中,我们的连接没有使用任何参数作为过滤器。如果您声明了使用参数作为过滤器的连接,则用于过滤器的值将成为连接标识符的一部分。换句话说,传递到连接过滤器中的每个值都将用于标识 Relay 存储中的连接。
请注意,这排除了分页参数,即它排除了 `first`、`last`、`before` 和 `after`。
例如,假设 `comments` 字段接受以下参数,我们将其作为 GraphQL 变量 传递。
const {graphql} = require('RelayModern');
const storyFragment = graphql`
fragment StoryComponent_story on Story {
comments(
order_by: $orderBy,
filter_mode: $filterMode,
language: $language,
) @connection(key: "StoryComponent_story_comments_connection") {
edges {
nodes {
body {
text
}
}
}
}
}
`;
在上面的示例中,这意味着我们查询 `comments` 字段时用于 `$orderBy`、`$filterMode` 和 `$language` 的任何值都将成为连接标识符的一部分,并且在从 Relay 存储访问连接记录时,我们需要使用这些值。
为了实现这一点,我们需要向 `ConnectionHandler.getConnection` 传递第三个参数,其中包含用于标识连接的具体过滤器值。
const {ConnectionHandler} = require('RelayModern');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
// Get the connection instance for the connection with comments sorted
// by the date they were added
const connectionRecordSortedByDate = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
{order_by: '*DATE_ADDED*', filter_mode: null, language: null}
);
// Get the connection instance for the connection that only contains
// comments made by friends
const connectionRecordFriendsOnly = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
{order_by: null, filter_mode: '*FRIENDS_ONLY*', language: null}
);
}
这意味着默认情况下,用于过滤器的值的每种组合将生成连接的不同记录。
更新连接时,您需要确保更新受更改影响的所有相关记录。例如,如果我们要向示例连接添加新评论,我们需要确保不将评论添加到 `FRIENDS_ONLY` 连接中,如果新评论不是由用户的某个朋友发布的。
const {ConnectionHandler} = require('relay-runtime');
function updater(store: RecordSourceSelectorProxy) {
const storyRecord = store.get(storyID);
// Get the connection instance for the connection with comments sorted
// by the date they were added
const connectionRecordSortedByDate = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
{order_by: '*DATE_ADDED*', filter_mode: null, language: null}
);
// Get the connection instance for the connection that only contains
// comments made by friends
const connectionRecordFriendsOnly = ConnectionHandler.getConnection(
storyRecord,
'StoryComponent_story_comments_connection',
{order_by: null, filter_mode: '*FRIENDS_ONLY*', language: null}
);
const newComment = (...);
const newEdge = (...);
ConnectionHandler.insertEdgeAfter(
connectionRecordSortedByDate,
newEdge,
);
if (isMadeByFriend(storyRecord, newComment) {
// Only add new comment to friends-only connection if the comment
// was made by a friend
ConnectionHandler.insertEdgeAfter(
connectionRecordFriendsOnly,
newEdge,
);
}
}
管理具有多个过滤器的连接
如您所见,仅向连接添加一些过滤器就可以使复杂性和需要管理的连接记录数量急剧增加。为了更轻松地管理这些记录,Relay 提供了两种策略。
1) 准确指定哪些过滤器应用作连接标识符。
默认情况下,所有非分页过滤器都将用作连接标识符的一部分。但是,在声明 `@connection` 时,您可以指定用于连接标识的精确过滤器集。
const {graphql} = require('relay-runtime');
const storyFragment = graphql`
fragment StoryComponent_story on Story {
comments(
order_by: $orderBy
filter_mode: $filterMode
language: $language
)
@connection(
key: "StoryComponent_story_comments_connection"
filters: ["order_by", "filter_mode"]
) {
edges {
nodes {
body {
text
}
}
}
}
}
`;
- 通过在声明 `@connection` 时指定 `filters`,我们向 Relay 指示应使用哪些确切的过滤器值集作为连接标识的一部分。在本例中,我们排除了 `language`,这意味着只有 `order_by` 和 `filter_mode` 的值会影响连接标识,从而生成新的连接记录。
- 从概念上讲,这意味着我们正在指定哪些参数会影响来自服务器的连接的输出,或者换句话说,哪些参数是实际的过滤器。如果连接参数之一实际上不更改从服务器返回的项目集或其排序,那么它就不是连接的真实过滤器,并且当该值更改时,我们不需要以不同的方式标识连接。在我们的示例中,更改我们请求的评论的 `language` 不会更改连接返回的评论集,因此可以安全地从 `filters` 中排除它。
- 如果我们知道连接参数中的任何参数在我们的应用程序中永远不会更改,这也很有用,在这种情况下,也可以安全地从 `filters` 中排除它。
2) 更简单的 API 替代方案用于管理具有多个过滤器值的多个连接,目前仍在等待开发中。
待定。
此页面是否有用?
通过以下方式帮助我们改进网站: 回答几个简短的问题。.