リンクされたフィールドの命令的な変更
前のセクションの例では、readUpdatableQuery
APIを使用して、is_new_comment
やis_selected
のようなスカラーフィールドを更新する方法を示しました。
この例では、リンクされたフィールドへの代入方法については触れませんでした。アプリケーションのユーザーがViewerのbest_friend
フィールドを更新できるようにするコンポーネントの例から始めましょう。
例:Viewerの親友の設定
Viewerの親友を割り当てるには、そのViewerがそのようなフィールドを持っている必要があります。これはサーバーのスキーマで定義することも、次のようにスキーマ拡張でローカルに定義することもできます。
extend type Viewer {
best_friend: User,
}
次に、フラグメントを定義し、@assignable
ディレクティブを与えて、それをassignableフラグメントにします。Assignnableフラグメントには、単一のフィールド__typename
のみを含めることができます。このフラグメントは、best_friend
フィールドの型であるUser
型になります。
// AssignBestFriendButton.react.js
graphql`
fragment AssignBestFriendButton_assignable_user on User @assignable {
__typename
}
`;
フラグメントは、ソース(つまり、Viewerの新しい親友)と、宛先(アップデーターブルクエリ内のViewerのbest_friend
フィールド内)の両方に展開する必要があります。
AssignableBestFriendButton_assignable_user
を展開するフラグメントを持つコンポーネントを定義しましょう。このユーザーがViewerの新しい親友になります。
// 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
に代入することを有効にするには、アップデーターブルクエリまたはフラグメント内のViewerの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
フィールドを読み取っていたすべてのコンポーネントは、必要に応じて再レンダリングされます。 - 割り当てのソースは、assignableフラグメントが展開されているユーザーです。
- 割り当てのターゲットは、
commitLocalUpdate
とreadUpdatableQuery
APIを使用してアクセスします。 readUpdatableQuery
に渡されるクエリには、@updatable
ディレクティブを含める必要があります。- ターゲットフィールドには、同じassignableフラグメントが展開されている必要があります。
- 割り当てる前に、
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
から(Node
を実装する)User
に割り当てる場合は、インラインフラグメントを使用して、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;
}
}
);
};
このスニペットでは、次の2つのことを行っています。
- インラインフラグメントを使用して、
Node
型をUser
型に絞り込みます。この絞り込みの中で、assignableフラグメントを展開します。 data.node.__typename === "User"
を確認します。これは、そのifブロック内で、data.node
がユーザーであることがわかっていることをFlowに示し、したがって、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,
重要な注意点は、セッターがnullでない__isFoo_actor
フィールドを持つオブジェクトを予期していることです。
抽象型を持つassignableフラグメントが通常のフラグメントで展開されると、タイプがインターフェイスを実装することがわかっている場合はオプションではなく、それ以外の場合はオプションである__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;
}
}
});
};
このフィールドの存在を推測するためにフローを使用できますか?
残念ながら、__isFoo_actor
の存在を確認しても、Flowは(型レベルで)フィールドがオプションではないとは推測しません。したがって、バリデーターを使用する必要があります。
このページは役に立ちましたか?
いくつかの簡単な質問に答えることで、このサイトをさらに改善するのにご協力ください。 いくつかの簡単な質問に答える.