跳至主要內容
版本:v18.0.0

更新連線

通常當您渲染連線時,您也會希望能夠根據使用者操作,在連線中新增或移除項目。

如我們的更新資料章節所述,Relay 保留一個正規化 GraphQL 資料的本機記憶體儲存,其中記錄會以其 ID 儲存。當使用 Relay 建立變異、訂閱或本機資料更新時,您必須提供一個 updater 函式,在其中您可以存取和讀取記錄,以及寫入和更新它們。當記錄更新時,任何受更新資料影響的元件都會收到通知並重新渲染。

連線記錄

在 Relay 中,以 @connection 指令標記的連線欄位會以特殊記錄儲存在儲存區中,並且它們會保存並累積到目前為止已為該連線擷取的所有項目。為了從連線新增或移除項目,我們需要使用宣告 @connection 時提供的連線 key 來存取連線記錄;具體來說,這允許我們使用 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 會將選取的邊緣新增至陣列中每個連線的結尾。

參數

範例

// 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 會將包含選取節點的邊緣新增至陣列中每個連線的結尾。

參數

範例

// 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 store API 來存取 mutation 回應中的 edge。

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,
);

// ...
}
  • mutation 的 payload 在 store 中會以 root field 的形式存在,可以使用 store.getRootField API 來讀取。在我們的例子中,我們讀取的是 comment_create,它是回應中的 root field。
  • 請注意,我們需要先使用 ConnectionHandler.buildConnectionEdge 從伺服器收到的 edge 來建構新的 edge,才能將其新增到 connection 中。

如果你需要從頭開始建立新的 edge,可以使用 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 */
);

// ...
}

一旦我們有了新的 edge 記錄,我們就可以使用 ConnectionHandler.insertEdgeAfterConnectionHandler.insertEdgeBefore 將其新增到 connection 中。

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 會**原地修改** connection。
注意

請查看我們完整的 Relay Store API

移除 edge

使用宣告式的刪除指令

新增 edge 的指令類似,我們可以使用 @deleteEdge 指令從 connection 中刪除 edge。如果你的 mutation 或 subscription 公開了一個欄位,其中包含已刪除的節點的 ID 或 IDs,並且你可以查詢回應,那麼你可以在該欄位上使用 @deleteEdge 指令,從 connection 中刪除相應的 edge(請注意,此指令也適用於查詢)。

@deleteEdge

適用於 GraphQL 欄位,該欄位會回傳 ID[ID]。將會從 connections 陣列中定義的每個 connection 中刪除節點與該 id 相符的 edge。

參數

範例

// 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],
},
});

手動移除 edge

ConnectionHandler 提供類似的 API 來從 connection 中移除 edge,透過 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** 來移除 edge。這表示它會在 connection 中查找包含具有提供的 ID 的節點的 edge,然後移除該 edge。
  • 請注意,此 API 會**原地修改** connection。
注意

請記住:當執行此處描述的任何操作來修改 connection 時,任何正在渲染受影響 connection 的 fragment 或查詢組件都將會收到通知,並以 connection 的最新版本重新渲染。

使用篩選器的 connection 識別

在我們之前的範例中,我們的 connection 沒有將任何參數作為篩選器。如果你宣告一個將參數作為篩選器的 connection,則用於篩選器的值將會是 connection 識別符的一部分。換句話說,*作為 connection 篩選器傳遞的每個值都會用於識別 Relay store 中的 connection。*

注意

請注意,這不包括分頁參數,即不包括 firstlastbeforeafter

例如,假設 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 的任何值都將是 connection 識別符的一部分,並且當我們從 Relay store 存取 connection 記錄時,我們需要使用這些值。

為了做到這一點,我們需要將第三個參數傳遞給 ConnectionHandler.getConnection,其中包含具體的篩選器值以識別 connection。

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}
);
}

這表示預設情況下,*用於篩選器的每個值組合都會為 connection 產生不同的記錄。*

當對 connection 進行更新時,你需要確保更新所有受更改影響的相關記錄。例如,如果我們要在範例 connection 中新增一則新留言,我們需要確保*不要*將留言新增到 FRIENDS_ONLY connection,如果新留言不是由使用者的朋友所發佈。

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,
);
}
}

管理具有許多篩選器的 connection

你可以看到,僅僅向 connection 新增一些篩選器就會使需要管理的 connection 記錄的複雜性和數量激增。為了更輕鬆地管理此問題,Relay 提供了 2 種策略。

1) 明確指定*哪些*篩選器應該用作 connection 識別符。

預設情況下,*所有*非分頁篩選器都將用作 connection 識別符的一部分。但是,當宣告 @connection 時,你可以指定用於 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 指示應作為 connection 識別一部分使用的確切篩選器值集。在這種情況下,我們排除 language,這表示只有 order_byfilter_mode 的值會影響 connection 識別,因此產生新的 connection 記錄。
  • 從概念上講,這表示我們指定哪些參數會影響來自伺服器的 connection 輸出,或者換句話說,哪些參數*實際上*是*篩選器*。如果 connection 的其中一個參數實際上不會更改從伺服器回傳的項目集或其順序,那麼它實際上並不是 connection 的篩選器,並且當該值更改時,我們不需要以不同的方式識別 connection。在我們的範例中,更改我們請求的留言的 language 不會更改 connection 回傳的留言集,因此可以安全地將其從 filters 中排除。
  • 如果我們知道 connection 的任何參數永遠不會在我們的應用程式中變更,這也很有用,在這種情況下,將其從 filters 中排除也是安全的。

2) 仍然在等待一個更簡單的 API 替代方案來管理具有多個篩選器值的多個 connection

待辦


這個頁面有幫助嗎?

透過以下方式協助我們讓網站變得更好 回答幾個快速問題.