強制修改連結欄位
上一節中的範例示範了如何使用 readUpdatableQuery
API 來更新純量欄位,例如 is_new_comment
和 is_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>);
}
太棒了!現在,我們有一個呈現按鈕的元件。讓我們使用 commitLocalUpdate
和 readUpdatableQuery
API 來指派 viewer.best_friend
,以填寫該按鈕的點擊處理常式。
- 為了使指派
data.user
給best_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
欄位的所有元件都會在必要時重新呈現。 - 指派的來源是一個使用者,其中散布著一個可指派的片段。
- 使用
commitLocalUpdate
和readUpdatableQuery
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 不會 (在類型層級) 推斷該欄位不是選擇性的。因此,我們需要使用驗證器。
這個頁面有幫助嗎?
請協助我們讓網站更好 回答幾個快速問題.