GraphQL 變異
在 GraphQL 中,伺服器上的資料是使用 GraphQL 變異 來更新的。變異是讀寫伺服器操作,它們既修改後端的資料,也允許您在同一個請求中查詢修改後的資料。
撰寫變異
GraphQL 變異看起來非常像查詢,只是它使用 mutation
關鍵字
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
id
viewer_does_like
like_count
}
}
}
- 上面的變異會修改伺服器資料,以「喜歡」指定的
Feedback
物件。 feedback_like
是一個變異根欄位(或簡稱變異欄位),它會更新後端的資料。
- 變異會分兩個步驟處理:首先,更新會在伺服器上處理,然後執行查詢。這確保您只會看到已做為變異回應的一部分更新的資料。
請注意,查詢的處理方式相同。外部選取會在內部選取之前計算。頂層變異欄位具有副作用,而其他欄位則傾向於沒有,這只是一個慣例問題。
- 變異欄位(在此例中為
feedback_like
)會傳回特定的 GraphQL 類型,該類型會公開我們可以在變異回應中查詢的資料。
- 在此例中,我們正在查詢已更新的意見回饋物件,包括已更新的
like_count
和viewer_does_like
的更新值,指出目前檢視者是否喜歡該意見回饋物件。
上述變異成功回應的範例可能如下所示
{
"feedback_like": {
"feedback": {
"id": "feedback-id",
"viewer_does_like": true,
"like_count": 1,
}
}
}
在 Relay 中,我們也可以使用 graphql
標籤宣告 GraphQL 變異
const {graphql} = require('react-relay');
const feedbackLikeMutation = graphql`
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
id
viewer_does_like
like_count
}
}
}
`;
- 請注意,變異也可以像查詢或片段一樣,參考 GraphQL 變數。
使用 useMutation
執行變異
為了在 Relay 中對伺服器執行變異,我們可以使用 commitMutation
和 useMutation API。讓我們來看看使用 useMutation
API 的範例
import type {FeedbackLikeData, LikeButtonMutation} from 'LikeButtonMutation.graphql';
const {useMutation, graphql} = require('react-relay');
function LikeButton({
feedbackId: string,
}) {
const [commitMutation, isMutationInFlight] = useMutation<LikeButtonMutation>(
graphql`
mutation LikeButtonMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
viewer_does_like
like_count
}
}
}
`
);
return <button
onClick={() => commitMutation({
variables: {
input: {id: feedbackId},
},
})}
disabled={isMutationInFlight}
>
Like
</button>
}
讓我們精簡一下這裡發生的事情。
useMutation
會將包含變異的 graphql 文字做為其唯一引數。- 它會傳回一個項目元組
- 一個回呼(我們稱之為
commitMutation
),它會接受一個UseMutationConfig
,以及 - 一個布林值,指出變異是否正在進行中。
- 一個回呼(我們稱之為
- 此外,
useMutation
接受 Flow 類型參數。如同查詢一樣,變異的 Flow 類型會從 Relay 編譯器產生的檔案中匯出。- 如果提供了此類型,則
UseMutationConfig
也會變成靜態類型。最佳做法是始終提供此類型。
- 如果提供了此類型,則
- 現在,當使用變異變數呼叫
commitMutation
時,Relay 會發出一個網路請求,執行伺服器上的feedback_like
欄位。在此範例中,這會找到變數指定的意見回饋,並在後端記錄使用者喜歡該意見回饋。 - 執行該欄位後,後端會選取已更新的意見回饋物件,並從中選取
viewer_does_like
和like_count
欄位。- 由於
Feedback
類型包含id
欄位,因此 Relay 編譯器會自動為id
欄位新增一個選取。
- 由於
- 收到變異回應後,Relay 會在儲存區中找到具有相符
id
的意見回饋物件,並使用新收到的viewer_does_like
和like_count
值來更新它。 - 如果這些值因此而變更,則任何從意見回饋物件中選取這些欄位的元件都會重新轉譯。或者,通俗地說,任何依賴更新資料的元件都會重新轉譯。
參數 FeedbackLikeData
的類型名稱衍生自頂層變異欄位的名稱,即 feedback_like
。此類型也會從產生的 graphql.js
檔案匯出。
回應變異重新整理元件
在先前的範例中,我們手動選取了 viewer_does_like
和 like_count
。如果這些欄位的值變更,則選取這些欄位的元件將會重新轉譯。
不過,通常最好散佈與我們想要回應變異重新整理的元件相對應的片段。這是因為元件選取的資料可能會變更。
要求開發人員了解所有可能影響其元件資料的變異(並使其保持最新狀態)是 Relay 想要避免要求的全局推理類型的一個範例。
例如,我們可以如下重寫變異
mutation FeedbackLikeMutation($input: FeedbackLikeData!) {
feedback_like(data: $input) {
feedback {
...FeedbackDisplay_feedback
...FeedbackDetail_feedback
}
}
}
如果執行此變異,則 FeedbackDisplay
和 FeedbackDetail
元件選取的所有欄位都會重新擷取,而且這些元件會保持一致的狀態。
散佈片段通常比在變異完成後重新擷取資料更好,因為可以在單次往返中擷取更新的資料。
在變異完成或發生錯誤時執行回呼
我們可能想要回應變異成功或失敗來更新某些狀態。例如,我們可能想要在變異失敗時提醒使用者。UseMutationConfig
物件可以包含下列欄位來處理這種情況
onCompleted
,當變異完成時執行的回呼。它會傳遞變異回應(在片段散佈界限處停止)。- 傳遞給
onCompleted
的值是變異片段,從儲存區讀取,在套用更新程式和宣告式變異指示詞之後。這表示不會讀取未遮罩片段中的資料,而且已刪除的記錄(例如,透過@deleteRecord
刪除)也可能為 null。
- 傳遞給
onError
,在變異發生錯誤時執行的回呼。它會傳遞發生的錯誤。
宣告式變異指示詞
在回應變異時操作連線
Relay 讓您能夠輕鬆地透過新增項目至連線(即清單)或從連線中移除項目來回應變異。例如,您可能想要將新建立的使用者附加至指定的連線。如需詳細資訊,請參閱使用宣告式指示詞。
在回應變異時刪除項目
此外,您可能想要在回應變異時從儲存區中刪除項目。為了執行此動作,您會將 @deleteRecord
指示詞新增至已刪除的 ID。例如
mutation DeletePostMutation($input: DeletePostData!) {
delete_post(data: $input) {
deleted_post {
id @deleteRecord
}
}
}
命令式地修改本機資料
有時,您希望執行的更新比單純更新欄位值更複雜,而且無法由宣告式變異指示詞處理。對於這種情況,UseMutationConfig
接受一個 updater
函式,讓您完全控制如何更新儲存區。
在命令式地修改儲存區資料一節中會更詳細地討論此內容。
樂觀更新
通常,我們不想在回應使用者互動之前等待伺服器回應。例如,如果使用者按一下「喜歡」按鈕,我們會希望立即顯示受影響的評論、貼文等已獲得使用者喜歡。
更廣泛來說,在這些情況下,我們希望立即樂觀地更新資料儲存區中的資料,也就是假設變更將會成功完成。如果變更最終沒有成功,我們希望回滾樂觀更新。
樂觀回應
為了啟用此功能,UseMutationConfig
可以包含一個 optimisticResponse
欄位。
為了使這個欄位具有 Flow 類型,對 useMutation
的呼叫必須傳遞一個 Flow 類型參數,而且變更必須以 @raw_response_type
指令裝飾。
在先前的範例中,我們可能會提供以下的樂觀回應
{
feedback_like: {
feedback: {
// Even though the id field is not explicitly selected, the
// compiler selected it for us
id: feedbackId,
viewer_does_like: true,
},
},
}
現在,當我們呼叫 commitMutation
時,這些資料會立即寫入儲存區。儲存區中具有相符 ID 的項目將會使用新的 viewer_does_like
值來更新。任何選取此欄位的元件都會重新渲染。
當變更成功或發生錯誤時,樂觀回應將會回滾。
更新 like_count
欄位需要多做一些工作。為了更新它,我們也應該讀取元件中的目前按讚計數。
import type {FeedbackLikeData, LikeButtonMutation} from 'LikeButtonMutation.graphql';
import type {LikeButton_feedback$fragmentType} from 'LikeButton_feedback.graphql';
const {useMutation, graphql} = require('react-relay');
function LikeButton({
feedback: LikeButton_feedback$fragmentType,
}) {
const data = useFragment(
graphql`
fragment LikeButton_feedback on Feedback {
__id
viewer_does_like @required(action: THROW)
like_count @required(action: THROW)
}
`,
feedback
);
const [commitMutation, isMutationInFlight] = useMutation<LikeButtonMutation>(
graphql`
mutation LikeButtonMutation($input: FeedbackLikeData!)
@raw_response_type {
feedback_like(data: $input) {
feedback {
viewer_does_like
like_count
}
}
}
`
);
const changeToLikeCount = data.viewer_does_like ? -1 : 1;
return <button
onClick={() => commitMutation({
variables: {
input: {id: data.__id},
},
optimisticResponse: {
feedback_like: {
feedback: {
id: data.__id,
viewer_does_like: !data.viewer_does_like,
like_count: data.like_count + changeToLikeCount,
},
},
},
})}
disabled={isMutationInFlight}
>
Like
</button>
}
您應該小心,並考慮使用樂觀更新器,如果樂觀回應的值取決於儲存區的值,而且如果有數個樂觀回應影響該儲存區值。
例如,如果兩個樂觀回應各自將按讚計數加一,且第一個樂觀更新器被回滾,則第二個樂觀更新仍會套用,且儲存區中的按讚計數仍會增加二。
樂觀回應包含許多陷阱!
- 樂觀回應可以包含完整查詢回應的資料,也就是包含片段散佈的內容。這表示,如果開發人員在片段散佈於樂觀回應中的元件中選取更多欄位,則這些元件在樂觀更新期間可能會具有不一致或不完整的資料。
- 由於樂觀更新的類型包含所有遞迴巢狀片段的內容,因此它可能非常大。將
@raw_response_type
新增至某些變更可能會降低 Relay 編譯器的效能。
樂觀更新器
樂觀回應並不足以應付所有情況。例如,我們可能想要樂觀地更新我們未在變更中選取的資料。或者,我們可能想要從連線新增或移除項目(而且宣告式變更指令不足以應付我們的使用案例)。
對於這些情況,UseMutationConfig
可以包含一個 optimisticUpdater
欄位,允許開發人員以命令式和樂觀地更新儲存區中的資料。這在關於命令式更新儲存區資料的章節中會詳細討論。
更新器函式的執行順序
一般而言,updater
和樂觀更新的執行順序如下
- 如果提供
optimisticResponse
,則該資料將會寫入儲存區。 - 如果提供
optimisticUpdater
,Relay 會執行它並相應地更新儲存區。 - 如果提供
optimisticResponse
,則變更中存在的宣告式變更指令將會在樂觀回應中處理。 - 如果變更請求成功
- 任何套用的樂觀更新都會被回滾。
- Relay 會將伺服器回應寫入儲存區。
- 如果提供
updater
,Relay 會執行它並相應地更新儲存區。伺服器酬載將會以儲存區中的根欄位形式提供給updater
。 - Relay 將會使用伺服器回應來處理任何宣告式變更指令。
- 將會呼叫
onCompleted
回呼。
- 如果變更請求失敗
- 任何套用的樂觀更新都會被回滾。
- 將會呼叫
onError
回呼。
在變更期間使資料失效
執行變更時建議的方法是從伺服器請求所有受變更影響的相關資料(作為變更主體的一部分),以便我們的本機 Relay 儲存區與伺服器的狀態一致。
然而,通常無法知道和指定對於具有大規模漣漪效應的變更(例如,想像「封鎖使用者」或「離開群組」)所有可能的受影響資料。
對於這些類型的變更,明確地將某些資料標記為過期(或整個儲存區)通常更直接,以便 Relay 知道下次呈現時要重新擷取它。若要這麼做,您可以使用我們的資料過期章節中記載的資料失效 API。
此頁面是否實用?
協助我們讓網站更臻完善,請 回答幾個簡單的問題.