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

強制修改連結欄位

上一節中的範例示範了如何使用 readUpdatableQuery API 來更新純量欄位,例如 is_new_commentis_selected

這些範例並涵蓋如何指派給連結的欄位。我們先來看一個範例,說明如何讓應用程式的使用者更新檢視器的 best_friend 欄位。

範例:設定檢視器的最佳好友

為了指派檢視器的最佳好友,該檢視器必須有這樣的欄位。這可以由伺服器架構定義,也可以在本機的架構擴充中定義,如下所示

extend type Viewer {
best_friend: User,
}

接下來,讓我們定義一個片段,並給它 @assignable 指令,使其成為一個可指派的片段。可指派的片段只能包含一個欄位,即 __typename。此片段將位於 User 類型上,這是 best_friend 欄位的類型。

// AssignBestFriendButton.react.js
graphql`
fragment AssignBestFriendButton_assignable_user on User @assignable {
__typename
}
`;

該片段必須同時散布於來源 (即檢視器的新最佳好友) 和目的地 (在可更新查詢中檢視器的 best_friend 欄位內)。

讓我們定義一個具有片段的元件,其中我們散布 AssignableBestFriendButton_assignable_user。此使用者將成為檢視器的新最佳好友。

// AssignBestFriendButton.react.js
import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';

const {useFragment} = require('react-relay');

export default function AssignBestFriendButton({
someTypeRef: AssignBestFriendButton_user$key,
}) {
const data = useFragment(graphql`
fragment AssignBestFriendButton_someType on SomeType {
user {
name
...AssignableBestFriendButton_assignable_user
}
}
`, someTypeRef);

// We will replace this stub with the real thing below.
const onClick = () => {};

return (<button onClick={onClick}>
Declare {data.user?.name ?? 'someone with no name'} your new best friend!
</button>);
}

太棒了!現在,我們有一個呈現按鈕的元件。讓我們使用 commitLocalUpdatereadUpdatableQuery API 來指派 viewer.best_friend,以填寫該按鈕的點擊處理常式。

  • 為了使指派 data.userbest_friend 有效,我們也必須在可更新查詢或片段中檢視器內的 best_friend 欄位下散布 AssignBestFriendButton_assignable_user
import type {RecordSourceSelectorProxy} from 'react-relay';

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

// ...

const environment = useRelayEnvironment();
const onClick = () => {
const updatableData = commitLocalUpdate(
environment,
(store: RecordSourceSelectorProxy) => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query AssignBestFriendButtonUpdatableQuery
@updatable {
viewer {
best_friend {
...AssignableBestFriendButton_assignable_user
}
}
}
`,
{}
);

if (data.user != null && updatableData.viewer != null) {
updatableData.viewer.best_friend = data.user;
}
}
);
};

整合在一起

完整範例如下

extend type Viewer {
best_friend: User,
}
// AssignBestFriendButton.react.js
import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';
import type {RecordSourceSelectorProxy} from 'react-relay';

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

graphql`
fragment AssignBestFriendButton_assignable_user on User @assignable {
__typename
}
`;

export default function AssignBestFriendButton({
someTypeRef: AssignBestFriendButton_someType$key,
}) {
const data = useFragment(graphql`
fragment AssignBestFriendButton_someType on SomeType {
user {
name
...AssignableBestFriendButton_assignable_user
}
}
`, someTypeRef);

const environment = useRelayEnvironment();
const onClick = () => {
const updatableData = commitLocalUpdate(
environment,
(store: RecordSourceSelectorProxy) => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query AssignBestFriendButtonUpdatableQuery
@updatable {
viewer {
best_friend {
...AssignableBestFriendButton_assignable_user
}
}
}
`,
{}
);

if (data.user != null && updatableData.viewer != null) {
updatableData.viewer.best_friend = data.user;
}
}
);
};

return (<button onClick={onClick}>
Declare {user.name ?? 'someone with no name'} my best friend!
</button>);
}

讓我們回顧一下這裡發生的事情。

  • 我們正在編寫一個元件,其中按一下按鈕會導致使用者被指派給 viewer.best_friend。按一下此按鈕後,先前讀取 viewer.best_friend 欄位的所有元件都會在必要時重新呈現。
  • 指派的來源是一個使用者,其中散布著一個可指派的片段
  • 使用 commitLocalUpdatereadUpdatableQuery API 存取指派的目標。
  • 傳遞給 readUpdatableQuery 的查詢必須包含 @updatable 指令。
  • 目標欄位必須散布相同的可指派片段
  • 我們正在檢查 data.user 是否不為 null,然後再指派。這不是絕對必要的。但是,如果我們指派 updatableData.viewer.best_friend = null,我們將在儲存區中將連結的欄位設為 null!這 (可能) 不是您想要的。

陷阱

  • 請注意,無法保證指派的使用者有哪些欄位存在。這表示任何使用更新欄位的人都無法保證已提取所需欄位,並且這些欄位存在於已指派的物件上。

範例:指派至清單

讓我們修改先前的範例,將使用者附加到最佳好友的清單中。在此範例中,以下原則是相關的

每個已指派的連結欄位 (即指派的右手邊) 都必須源自唯讀的片段、查詢、變更或訂閱

這表示 updatableData.foo = updatableData.foo 無效。基於相同的原因,updatableData.viewer.best_friends = updatableData.viewer.best_friends.concat([newBestFriend]) 無效。為了避開此限制,我們必須從唯讀的片段選取現有的最佳好友,並按如下方式執行指派:viewer.best_friends = existing_list.concat([newBestFriend])

請考量以下完整範例

extend type Viewer {
# We are now defined a "best_friends" field instead of a "best_friend" field
best_friends: [User!],
}
// AssignBestFriendButton.react.js
import type {AssignBestFriendButton_user$key} from 'AssignBestFriendButton_user.graphql';
import type {AssignBestFriendButton_viewer$key} from 'AssignBestFriendButton_viewer';

import type {RecordSourceSelectorProxy} from 'react-relay';

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

graphql`
fragment AssignBestFriendButton_assignable_user on User @assignable {
__typename
}
`;

export default function AssignBestFriendButton({
someTypeRef: AssignBestFriendButton_someType$key,
viewerFragmentRef: AssignBestFriendButton_viewer$key,
}) {
const data = useFragment(graphql`
fragment AssignBestFriendButton_someType on SomeType {
user {
name
...AssignableBestFriendButton_assignable_user
}
}
`, someTypeRef);

const viewer = useFragment(graphql`
fragment AssignBestFriendButton_viewer on Viewer {
best_friends {
# since viewer.best_friends appears in the right hand side of the assignment
# (i.e. updatableData.viewer.best_friends = viewer.best_friends.concat(...)),
# the best_friends field must contain the correct assignable fragment spread
...AssignableBestFriendButton_assignable_user
}
}
`, viewerRef);

const environment = useRelayEnvironment();
const onClick = () => {
commitLocalUpdate(
environment,
(store: RecordSourceSelectorProxy) => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query AssignBestFriendButtonUpdatableQuery
@updatable {
viewer {
best_friends {
...AssignableBestFriendButton_assignable_user
}
}
}
`,
{}
);

if (data.user != null && updatableData.viewer != null && viewer.best_friends != null) {
updatableData.viewer.best_friends = [
...viewer.best_friends,
data.user,
];
}
}
);
};

return (<button onClick={onClick}>
Add {user.name ?? 'someone with no name'} to my list of best friends!
</button>);
}

範例:從抽象欄位指派到具體欄位

如果您要從抽象欄位 (例如,Node) 指派到 User (實作 Node),您必須使用內嵌片段將 Node 類型精簡為 User。請考量此程式碼片段

const data = useFragment(graphql`
fragment AssignBestFriendButton_someType on Query {
node(id: "4") {
... on User {
__typename
...AssignableBestFriendButton_assignable_user
}
}
}
`, queryRef);

const environment = useRelayEnvironment();
const onClick = () => {
const updatableData = commitLocalUpdate(
environment,
(store: RecordSourceSelectorProxy) => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query AssignBestFriendButtonUpdatableQuery
@updatable {
viewer {
best_friend {
...AssignableBestFriendButton_assignable_user
}
}
}
`,
{}
);

if (data.node != null && data.node.__typename === "User" && updatableData.viewer != null) {
updatableData.viewer.best_friend = data.node;
}
}
);
};

在此程式碼片段中,我們執行兩件事

  • 我們使用內嵌片段將 Node 類型精簡為 User 類型。在此精簡內部,我們散布可指派的片段。
  • 我們檢查 data.node.__typename === "User"。這向 Flow 表示在該 if 區塊內,已知 data.node 是使用者,因此 updatableData.viewer.best_friend = data.node 可以進行類型檢查。

範例:將介面指派給當來源保證實作該介面時

您可能希望指派給具有介面類型的目的地欄位 (在此範例中為 Actor)。如果來源欄位保證會實作該介面,則指派很簡單。

例如,來源可能具有相同的介面類型,或具有實作該介面的具體類型 (在此範例中為 User)。

請考量以下程式碼片段

graphql`
fragment Foo_actor on Actor @assignable {
__typename
}
`;

const data = useFragment(graphql`
fragment Foo_query on Query {
user {
...Foo_actor
}
viewer {
actor {
...Foo_actor
}
}
}
`, queryRef);

const environment = useRelayEnvironment();
const onClick = () => {
commitLocalUpdate(environment, store => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query FooUpdatableQuery @updatable {
viewer {
actor {
...Foo_actor
}
}
}
`,
{}
);

// Assigning the user works as you would expect
if (updatableData.viewer != null && data.user != null) {
updatableData.viewer = data.user;
}

// As does assigning the viewer
if (updatableData.viewer != null && data.viewer?.actor != null) {
updatableData.viewer = data.viewer.actor;
}
});
};

範例:將介面指派給當來源保證實作該介面時

您可能希望指派給具有介面類型的目的地欄位 (在此範例中為 Actor)。如果來源類型 (例如,Node) 未知會實作該介面,則會涉及額外的步驟:驗證。

為了了解原因,必須先了解一些背景資訊。介面欄位的 setter 的 flow 類型可能如下所示

set actor(value: ?{
+__id: string,
+__isFoo_actor: string,
+$fragmentSpreads: Foo_actor$fragmentType,
...
}): void,

需要注意的重要事項是,setter 預期具有非 null __isFoo_actor 欄位的物件。

當具有抽象類型的可指派片段散布在一般片段中時,如果已知類型實作介面,則會產生一個非選擇性的 __isFoo_actor: string 選取項目,否則會產生選擇性的選取項目。

由於 Node 保證會實作 Actor,因此當 Relay 編譯器遇到選取項目 node(id: "4") { ...Foo_actor } 時,它會發出一個選擇性欄位 (__isFoo_actor?: string)。嘗試將此欄位指派給 updatableData.viewer.actor 將無法進行類型檢查!

導入驗證器

每個產生的成品所產生的檔案都包含一個名為 validator 的匯出。在我們的範例中,該函式如下所示

function validate(value/*: {
+__id: string,
+__isFoo_actor?: string,
+$fragmentSpreads: Foo_actor$fragmentType,
...
}*/)/*: false | {
+__id: string,
+__isFoo_actor: string,
+$fragmentSpreads: Foo_actor$fragmentType,
...
}*/ {
return value.__isFoo_actor != null ? (value/*: any*/) : false;
}

換句話說,此函式會檢查 __isFoo_actor 欄位是否存在。如果找到,它會傳回相同的物件,但使用適用於指派的 flow 類型。如果找不到,則會傳回 false。

範例

讓我們在範例中將所有這些整合在一起

import {validate as validateActor} from 'Foo_actor.graphql';

graphql`
fragment Foo_actor on Actor @assignable {
__typename
}
`;

const data = useFragment(graphql`
fragment Foo_query on Query {
node(id: "4") {
...Foo_actor
}
}
`, queryRef);

const environment = useRelayEnvironment();
const onClick = () => {
commitLocalUpdate(environment, store => {
const {updatableData} = store.readUpdatableQuery(
graphql`
query FooUpdatableQuery @updatable {
viewer {
actor {
...Foo_actor
}
}
}
`,
{}
);

if (updatableData.viewer != null && data.node != null) {
const validActor = validateActor(data.node);
if (validActor !== false) {
updatableData.viewer.actor = validActor;
}
}
});
};

Flow 可以用來推斷此欄位的存在嗎?

不幸的是,如果您檢查 __isFoo_actor 的存在,Flow 不會 (在類型層級) 推斷該欄位不是選擇性的。因此,我們需要使用驗證器。


這個頁面有幫助嗎?

請協助我們讓網站更好 回答幾個快速問題.