命令式修改链接字段
上一节中的示例演示了如何使用 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
,我们将使存储中的链接字段为空!这(可能是)不是你想要的。
陷阱
- 请注意,没有关于已分配用户上存在哪些字段的保证。这意味着任何使用更新字段的人都无法保证所需的字段已被获取并存在于已分配对象上。
示例:分配给列表
让我们修改前面的示例,将用户追加到最佳朋友列表中。在本示例中,以下原则很重要
每个已分配的链接字段(即分配的右侧)必须起源于只读片段、查询、变异或订阅。
这意味着 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
)不知道是否实现了该接口,则需要一个额外的步骤:验证。
为了理解原因,需要一些背景知识。接口字段的设置器的流类型可能如下所示
set actor(value: ?{
+__id: string,
+__isFoo_actor: string,
+$fragmentSpreads: Foo_actor$fragmentType,
...
}): void,
需要注意的重要一点是,设置器需要一个具有非空 __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
字段是否存在。如果找到,它将返回相同对象,但流类型对于分配是有效的。如果没有找到,则返回 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 不会推断(在类型级别)该字段不是可选的。因此,我们需要使用验证器。
此页面有用吗?
通过以下操作帮助我们使网站更好 回答一些简短的问题.