RelayにおけるuseSubscriptionの使用例
概要
Relayでデータの変更を動的に検知、取得する仕組み
に公式のAPI説明があるが、情報が不足しているため、まとめておく
前提条件
- subscriptionを使うためには、GraphQLサーバー側がsubscriptionに対応している必要がある。
- websocketで通信を行う
- subscription clientの設定を書く必要がある
スキーマ
import { graphql } from 'relay-runtime';
export default graphql`
subscription sampleSubscription {
chatList {
messages {
text
}
}
}
`;
コンポーネント
import { FC, useMemo, useState } from 'react';
import { useSubscription } from 'react-relay';
import subscription from '@/subscriptions/sample';
import { sampleSubscription } from '@/subscriptions/__generated__/sampleSubscription.graphql';
export { sample as getServerSideProps } from '@/controllers/relay/sample';
const Sample: FC = () => {
const [data, setData] = useState<string[]>([]);
useSubscription<sampleSubscription>(
useMemo(
() => ({
variables: {},
subscription,
onNext: (data) => {
if (data?.messages) {
setData(data.messages.map(({ text }) => text));
}
},
}),
[]
)
);
return <>{data.map((item) => item)}</>;
};
export default Sample;
onNext
にデータが返ってくるので、それをステートに突っ込む
Relayは通常preloadをしてキャッシュに詰め込むが、ここはステートに突っ込むっぽい感じがしている
差分だけがonNext
に来るのかなーと思ったけど、全部きてそうだった
コンポーネントがアンマウントした時にunsubscribeしないといけない気がするけど、それはどうするんだろう?
useSubscription
の第二引数に何かがなげれそう
Arguments
config: a config of type GraphQLSubscriptionConfig passed to requestSubscription
requestSubscriptionFn: ?<TSubscriptionPayload>(IEnvironment, GraphQLSubscriptionConfig<TSubscriptionPayload>) => Disposable. An optional function with the same signature as requestSubscription, which will be called in its stead. Defaults to requestSubscription.
Behavior
This is only a thin wrapper around the requestSubscription API. It will:
Subscribe when the component is mounted with the given config
Unsubscribe when the component is unmounted
Unsubscribe and resubscribe with new values if the environment, config or requestSubscriptionFn changes.
If you have the need to do something more complicated, such as imperatively requesting a subscription, please use the requestSubscription API directly.
See the GraphQL Subscriptions Guide for a more detailed explanation of how to work with subscriptions.
ビヘイビア
これはrequestSubscription APIの薄いラッパーに過ぎません。これは、以下のようになります:
与えられたコンフィグでコンポーネントがマウントされたときにサブスクライブします。
コンポーネントがアンマウントされたときに配信を停止する
環境、設定、requestSubscriptionFnが変更された場合、新しい値で購読を解除し、再購読することができます。
サブスクリプションを強制的に要求するなど、より複雑なことを行う必要がある場合は、requestSubscription APIを直接使用してください。
サブスクリプションの操作方法についてのより詳細な説明については、『GraphQL Subscriptions Guide』を参照してください。
何も投げなくても勝手にしてくれるっぽい
ローディングは多分、suspenseが発火しているのではなかろうかと思ってるけど、一応検証
メモ
ちなみに、特定の条件がくるまでsubscribeをスキップしたいみたいなものについて
で話されてる
subscriptionとfragmentを同時に使う場合どうなるのか?
subscriptionでもfragmentを使いたいと思うけど、onNext
でしか入手できなかったらstateにぶち込むしかない?
普通にそのままcomponentにref渡したいけど、、
色々話してくれてそう
なんか根本的に使い方を間違えている気がしてきた
updaterでstoreにアクセスしているものが出てきたから、あくまでもsubscriptionはデータが更新された時に更新部分をsubscriptionでwatchして、updaterでstoreを更新するというだけに使う説
なので、実際にはqueryで叩いて、差分が現れる部分(チャットだと最新のチャットを読み込むとかそういう部分)のみをwatchしてupdaterでstoreを更新するみたいな使い方説
こういうやつ(full exampleが見つかってないのでなんともだけど
これだとすると、そもそもgraphqlのsubscription自体、そういうものということになるので、graphql subscriptionのexampleを探してみる
Apolloが結構良さげなドキュメントを書いてくれている
やっぱりそうっぽい
ということで作り直してみる
データはusePreloadedQueryで取得
updaterで更新をかける
subscriptionでのupdaterのサンプルコードは見当たらなかったが、mutationだとあったのでそれを参考にする
function sharedUpdater(
store: RecordSourceSelectorProxy,
userId: string,
newEdge: RecordProxy
) {
const userProxy = store.get(userId);
if (!userProxy) {
return;
}
const connection = ConnectionHandler.getConnection(
userProxy,
"TodoList_todos"
);
if (!connection) {
return;
}
ConnectionHandler.insertEdgeAfter(connection, newEdge);
}
UserCollectionの中のTodoなので、storeにはuserIdに紐づいてデータを持っているということかな?
だからそのプロキシを渡す必要がある?
あとは、このuserIdというのが、おそらくNode Interfaceにおける全テーブルにおいてグローバルユニークなIDのことを指している気がするので、Node Interfaceを使っていない場合はどのように入ってるんだろう?(設定次第?
あとはconnectionのところが鬼門
サンプルをみる感じ、@connection
ディレクティブで定義された"TodoList_todos"
を参照してそうだけど、@connection
ディレクティブを指定するためには、Relay形式に沿っている必要があって、edge
、node
の形に沿っていないとエラーになる気がする
これは、、なかなか骨が折れそうだな、、
をみる限り、どうやらNode IDがちゃんと付与されている場合は、↑のようにごちゃごちゃしなくても自動的にキャッシュデータが書き変わるらしい。
そして、
のように、fragmentをそのままsubscribeにしてやるみたいなこともできるらしいので、fragmentコロケーションを崩さずにできるっぽい(素晴らしい)
ただ、僕達のチームではNode Interfaceに沿ったスキーマ設計ができている状況ではないので、この手法だと難しい。
上記理由から、動的な部分については、一旦
のような形で実現させる
ゆくゆくは
の形式に沿ったスキーマ設計にしたいと思っているので、その時に再度チャレンジする
HasuraがRelayモードをちゃんとサポートしてくれればいいのに🫠