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

強制修改儲存資料

Relay 儲存區中的資料可以在更新器函式中強制修改。

何時使用更新器

複雜的客戶端更新

如果對本地資料的變更比單純將網路回應寫入儲存區所能實現的更複雜,且無法透過宣告式變異指示詞處理,您可能會提供更新器函式。

客戶端結構描述擴充

此外,由於網路回應必然不包含在客戶端結構描述擴充中定義的欄位資料,您可能會希望使用更新器來初始化在客戶端結構描述擴充中定義的資料。

使用其他 API

最後,有些事情您只能使用更新器才能實現,例如使節點失效、刪除節點、尋找給定欄位的所有連線等。

如果多個樂觀回應修改給定的儲存值

如果兩個樂觀回應影響給定的值,且第一個樂觀回應被回滾,則第二個樂觀回應將保持應用。

例如,如果兩個樂觀回應都將故事的讚數增加一,且第一個樂觀回應被回滾,則第二個樂觀回應仍然適用。但是,它將**不會重新計算**,並且讚數的值將保持增加二。

何時**不**使用更新器

觸發其他副作用

您應該使用 onCompleted 回呼來觸發其他副作用。 onCompleted 回呼保證會被呼叫一次,但更新器和樂觀更新器可能會被重複呼叫。

各種更新器函式的類型

useMutationcommitMutation API 接受可以包含 optimisticUpdaterupdater 欄位的組態物件。 requestSubscriptionuseSubscription API 接受可以包含 updater 欄位的組態物件。

此外,還有另一個 API (commitLocalUpdate) 也接受更新器函式。將在用於修改本地資料的其他 API章節中討論。

樂觀更新器與更新器

變異可以同時具有樂觀和常規更新器。觸發變異時會執行樂觀更新器。當該變異完成或錯誤時,樂觀更新會被回滾。

當變異成功完成時,會執行常規更新器。

範例

讓我們建構一個範例,其中在變異更新器中,將新建立的 Feedback 物件上的 is_new_comment 欄位(在結構描述擴充中定義)設定為 true

# Feedback.graphql
extend type Feedback {
is_new_comment: Boolean
}
// CreateFeedback.js
import type {Environment} from 'react-relay';
import type {
FeedbackCreateData,
CreateFeedbackMutation,
CreateFeedbackMutation$data,
} from 'CreateFeedbackMutation.graphql';

const {commitMutation, graphql} = require('react-relay');
const {ConnectionHandler} = require('relay-runtime');

function commitCreateFeedbackMutation(
environment: Environment,
input: FeedbackCreateData,
) {
return commitMutation<FeedbackCreateData>(environment, {
mutation: graphql`
mutation CreateFeedbackMutation($input: FeedbackCreateData!) {
feedback_create(input: $input) {
feedback {
id
# Step 1: in the mutation response, spread an updatable fragment (defined below).
# This updatable fragment will select the fields that we want to update on this
# particular feedback object.
...CreateFeedback_updatable_feedback
}
}
}
`,
variables: {input},

// Step 2: define an updater
updater: (store: RecordSourceSelectorProxy, response: ?CreateCommentMutation$data) => {
// Step 3: Access and nullcheck the feedback object.
// Note that this could also have been achieved with the @required directive.
const feedbackRef = response?.feedback_create?.feedback;
if (feedbackRef == null) {
return;
}

// Step 3: call store.readUpdatableFragment
const {updatableData} = store.readUpdatableFragment(
// Step 4: Pass it a fragment literal, where the fragment contains the @updatable directive.
// This fragment selects the fields that you wish to update on the feedback object.
// In step 1, we spread this fragment in the query response.
graphql`
fragment CreateFeedback_updatable_feedback on Feedback @updatable {
is_new_comment
}
`,
// Step 5: Pass the fragment reference.
feedbackRef
);

// Step 6: Mutate the updatableData object!
updatableData.is_new_comment = true;
},
});
}

module.exports = {commit: commitCreateFeedbackMutation};

讓我們提煉這裡正在發生的事情。

  • updater 接受兩個參數:一個 RecordSourceSelectorProxy 和一個可選物件,它是讀取變異回應的結果。
    • 第二個引數的類型是從產生的變異檔案匯入的 $data 類型的可為空版本。
    • 第二個引數僅包含由變異引數直接選取的資料。換句話說,它不會包含僅由擴散片段選取的任何欄位。
  • updater 在變異回應已寫入儲存區後執行。
  • 在此範例更新器中,我們執行三件事
    • 首先,我們在變異回應中擴散一個可更新的片段。
    • 其次,我們透過呼叫 readUpdatableFragment 讀取此片段選取的欄位。這會傳回一個可更新的 Proxy 物件。
    • 第三,我們更新此可更新 Proxy 上的欄位。
  • 一旦此更新器完成,記錄的更新就會寫入儲存區,並且所有受影響的元件都會重新渲染。

範例 2:更新資料以回應使用者互動

讓我們考慮更新儲存資料以回應使用者互動的常見情況。在點擊處理常式中,讓我們切換 is_selected 欄位。此欄位定義在客戶端結構描述擴充中的 Users 上。

# User.graphql
extend type User {
is_selected: Boolean
}
// UserSelectToggle.react.js
import type {RecordSourceSelectorProxy} from 'react-relay';
import type {UserSelectToggle_viewer$key} from 'UserSelectToggle_viewer.graphql';

const {useRelayEnvironment, commitLocalUpdate} = require('react-relay');

function UserSelectToggle({ userId, viewerRef }: {
userId: string,
viewerRef: UserSelectToggle_viewer$key,
}) {
const viewer = useFragment(graphql`
fragment UserSelectToggle_viewer on Viewer {
user(user_id: $user_id) {
id
name
is_selected
...UserSelectToggle_updatable_user
}
}
`, viewerRef);

const environment = useRelayEnvironment();

return <button
onClick={() => {
commitLocalUpdate(
environment,
(store: RecordSourceSelectorProxy) => {
const userRef = viewer.user;
if (userRef == null) {
return;
}

const {updatableData} = store.readUpdatableFragment(
graphql`
fragment UserSelectToggle_updatable_user on User @updatable {
is_selected
}
`,
userRef
);

updatableData.is_selected = !viewer?.user?.is_selected;
}
);
}}
>
{viewer?.user?.is_selected ? 'Deselect' : 'Select'} {viewer?.user?.name}
</button>
}

讓我們提煉這裡正在發生的事情。

  • 在點擊處理常式中,我們呼叫 commitLocalUpdate,它接受 Relay 環境和一個更新器函式。**與先前的範例不同,此更新器不接受第二個參數**,因為沒有相關的網路酬載。
  • 在此更新器函式中,我們透過呼叫 store.readUpdatableFragment 來存取可更新的 Proxy 物件,並切換 is_selected 欄位。
  • 與先前呼叫 readUpdatableFragment 的範例一樣,這可以重寫為使用 readUpdatableQuery API。
注意

可以使用 environment.commitPayload API 重寫此範例,儘管沒有類型安全。

替代 API:readUpdatableQuery

在先前的範例中,我們使用可更新的片段來存取我們要更新其欄位的記錄。也可以使用可更新的查詢來執行此操作。

如果我們知道從根 (即,類型為 Query 的物件) 到我們要修改的記錄的路徑,我們可以利用 readUpdatableQuery API 來實現此目的。

例如,我們可以設定檢視器的 name 欄位以回應事件,如下所示

// NameUpdater.react.js
function NameUpdater({ queryRef }: {
queryRef: NameUpdater_viewer$key,
}) {
const environment = useRelayEnvironment();
const data = useFragment(
graphql`
fragment NameUpdater_viewer on Viewer {
name
}
`,
queryRef
);
const [newName, setNewName] = useState(data?.viewer?.name);
const onSubmit = () => {
commitLocalUpdate(environment, store => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query NameUpdaterUpdateQuery @updatable {
viewer {
name
}
}
`,
{}
);
const viewer = updatableData.viewer;
if (viewer != null) {
viewer.name = newName;
}
});
};

// etc
}
  • 此特定範例可以使用 readUpdatableFragment 重寫。但是,您可能會因以下幾個原因而偏好 readUpdatableQuery
    • 您沒有現成的片段參考,例如,如果對 commitLocalUpdate 的呼叫與元件沒有明顯關聯。
    • 您沒有現成的片段,在其中我們選取要修改的記錄的**父記錄** (例如,此範例中的 Query)。由於 Relay 中已知的類型漏洞,**可更新的片段無法在頂層擴散。**
    • 您希望在可更新的片段中使用變數。目前,可更新的片段會重複使用傳遞給查詢的變數。這表示您無法,例如,擁有一個具有片段本機變數的可更新片段,並多次呼叫 readUpdatableFragment,每次都傳遞不同的變數。

此頁面是否有用?

協助我們透過 回答幾個快速問題.