Closed31

RelayにおけるuseSubscriptionの使用例

kobokobo

スキーマ

import { graphql } from 'relay-runtime';

export default graphql`
  subscription sampleSubscription {
    chatList {
      messages {
        text
      }
    }
  }
`;
kobokobo

コンポーネント

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をしてキャッシュに詰め込むが、ここはステートに突っ込むっぽい感じがしている

kobokobo

差分だけがonNextに来るのかなーと思ったけど、全部きてそうだった

kobokobo

コンポーネントがアンマウントした時にunsubscribeしないといけない気がするけど、それはどうするんだろう?

kobokobo

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.

kobokobo

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』を参照してください。

kobokobo

何も投げなくても勝手にしてくれるっぽい

kobokobo

ローディングは多分、suspenseが発火しているのではなかろうかと思ってるけど、一応検証

kobokobo

あーそうか、普通にstateを表示してるだけだから、やるなら自分でやれということか

kobokobo

subscriptionとfragmentを同時に使う場合どうなるのか?

subscriptionでもfragmentを使いたいと思うけど、onNextでしか入手できなかったらstateにぶち込むしかない?

普通にそのままcomponentにref渡したいけど、、

kobokobo

なんか根本的に使い方を間違えている気がしてきた

kobokobo

updaterでstoreにアクセスしているものが出てきたから、あくまでもsubscriptionはデータが更新された時に更新部分をsubscriptionでwatchして、updaterでstoreを更新するというだけに使う説

kobokobo

なので、実際にはqueryで叩いて、差分が現れる部分(チャットだと最新のチャットを読み込むとかそういう部分)のみをwatchしてupdaterでstoreを更新するみたいな使い方説

kobokobo

これだとすると、そもそもgraphqlのsubscription自体、そういうものということになるので、graphql subscriptionのexampleを探してみる

kobokobo

ということで作り直してみる

kobokobo

データはusePreloadedQueryで取得

updaterで更新をかける

subscriptionでのupdaterのサンプルコードは見当たらなかったが、mutationだとあったのでそれを参考にする

https://codesandbox.io/s/relay-sandbox-nxl7i?file=/src/useAddTodoMutation.ts:613-637

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);
}
kobokobo

UserCollectionの中のTodoなので、storeにはuserIdに紐づいてデータを持っているということかな?

だからそのプロキシを渡す必要がある?

あとは、このuserIdというのが、おそらくNode Interfaceにおける全テーブルにおいてグローバルユニークなIDのことを指している気がするので、Node Interfaceを使っていない場合はどのように入ってるんだろう?(設定次第?

kobokobo

あとはconnectionのところが鬼門

サンプルをみる感じ、@connectionディレクティブで定義された"TodoList_todos"を参照してそうだけど、@connectionディレクティブを指定するためには、Relay形式に沿っている必要があって、edgenodeの形に沿っていないとエラーになる気がする

kobokobo

これは、、なかなか骨が折れそうだな、、

kobokobo

https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/

をみる限り、どうやらNode IDがちゃんと付与されている場合は、↑のようにごちゃごちゃしなくても自動的にキャッシュデータが書き変わるらしい。

そして、

https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/#refreshing-components-in-response-to-subscription-events

のように、fragmentをそのままsubscribeにしてやるみたいなこともできるらしいので、fragmentコロケーションを崩さずにできるっぽい(素晴らしい)

kobokobo

ただ、僕達のチームではNode Interfaceに沿ったスキーマ設計ができている状況ではないので、この手法だと難しい。

このスクラップは2023/04/19にクローズされました