メインコンテンツにスキップ
バージョン: v18.0.0

リンクされたフィールドの命令的な変更

前のセクションの例では、readUpdatableQuery APIを使用して、is_new_commentis_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>);
}

素晴らしいですね。これで、ボタンをレンダリングするコンポーネントができました。commitLocalUpdatereadUpdatableQuery APIを使用して、viewer.best_friendを割り当てることで、そのボタンのクリックハンドラーを埋めていきましょう。

  • data.userbest_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フラグメントが展開されているユーザーです。
  • 割り当てのターゲットは、commitLocalUpdatereadUpdatableQuery 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選択になります。

NodeActorを実装することが保証されていないため、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は(型レベルで)フィールドがオプションではないとは推測しません。したがって、バリデーターを使用する必要があります。


このページは役に立ちましたか?

いくつかの簡単な質問に答えることで、このサイトをさらに改善するのにご協力ください。 いくつかの簡単な質問に答える.