Open4

SWRのコードリーディング

nakaakistnakaakist

keyの比較

keyの比較は、stableHashという関数により、深い比較を行うようになっている。実装は下記。
https://github.com/vercel/swr/blob/main/_internal/utils/hash.ts#L20

深い比較を行うために、SWRは与えられたキーをhash化する。
たとえば、keyが{ a: 'b', c: [2, 3] }だった場合、#c:@2,3,,a:"b",のようにhashになる。

また、条件付きfetchのために関数がkeyとして与えられた場合、その関数を一旦実行してみる。
それがエラーになれば、まだfetchすべきでないと判断し、keyを空文字列に。
エラーにならなければ、関数の返り値をhash化する。

ちなみに、useSWRの返り値のdataの比較にも、デフォルトでこのstableHashが使われており、
dataオブジェクトの参照が変化しても、dataは変化したとみなされず、re-renderがトリガされないようになっている
https://github.com/vercel/swr/blob/main/_internal/utils/config.ts#L40
https://github.com/vercel/swr/blob/main/core/use-swr.ts#L113

nakaakistnakaakist

キャッシュの持ち方

SWRが内部で持っているキャッシュは、cache providerに保存される。

デフォルトでは、Mapがキャッシュプロバイダとして使われる。
https://github.com/vercel/swr/blob/main/_internal/utils/config.ts#L44

キャッシュの中身

providerに保持されるキャッシュは、下記のようにdata, error, isValidating, isLoadingからなるState。

export type State<Data = any, Error = any> = {
  data?: Data
  error?: Error
  isValidating?: boolean
  isLoading?: boolean
}

https://github.com/vercel/swr/blob/main/_internal/types.ts#L430
https://github.com/vercel/swr/blob/main/_internal/types.ts#L334

SWRGlobalState

キャッシュプロバイダは、SWRGlobalStateというものに保存される。
https://github.com/vercel/swr/blob/main/_internal/utils/cache.ts#L77

SWRGlobalStateは、SWR全体で共有するWeakMap
https://github.com/vercel/swr/blob/main/_internal/utils/global-state.ts#L4

SWRGlobalStateは、キャッシュプロバイダをキーとして、下記のようなものを保持する。
https://github.com/vercel/swr/blob/main/_internal/types.ts#L4

 [
  Record<string, RevalidateCallback[]>, // EVENT_REVALIDATORS
  Record<string, [number, number]>, // MUTATION: [ts, end_ts]
  Record<string, [any, number]>, // FETCH: [data, ts]
  Record<string, FetcherResponse<any>>, // PRELOAD
  ScopedMutator, // Mutator
  (key: string, value: any, prev: any) => void, // Setter
  (key: string, callback: (current: any, prev: any) => void) => () => void // Subscriber
]

下記それぞれの詳細(読み取れる範囲)。

nakaakistnakaakist

Dependency Collectionの仕組み

useSWRの返り値のうち、必要なものだけをコンポーネント側で取得することで、それ以外の返り値の変更時にre-renderingが走らないという仕組みがある

これを実現する仕組みは下記。

  • useSyncExternalStoreを使って、下記設定を行う。
    • 設定
      • cache providerが保持するstateが変化し、
      • かつisEqual関数の結果がfalseなら、
      • stateのスナップショット取得処理を呼び、
      • それをcached変数に格納する
    • https://github.com/vercel/swr/blob/main/core/use-swr.ts#L203
    • ここで使っているsubscribeCacheという関数は、前述のSWRGlobalStateのSubscriberのラッパーで、キャッシュに保存されたstateが変化したときに呼ばれるコールバックを登録するもの。
    • useSyncExternalStoreの仕様的には、スナップショット取得処理が呼ばれた時点でre-renderをトリガする感じになる。
  • isEqual関数では、stateのうち、stateDependenciesに入っているプロパティのみをチェックしている。
  • useSWRの返り値のプロパティがなんらかの形でアクセスされると、get関数が走るようになっており、そこでstateDependenciesにプロパティが追加される形になっている。

つまり、例えば、 useSWRの返り値のisValidatingになんらかの形でアクセスすると、

  1. get関数の中でstateDependenciesisValidatingが追加される
  2. その後、isValidatingが変化すると、useSyncExternalStoreに登録しておいた処理の中で、isEqualがfalseになり、スナップショット取得処理が実行される
  3. これにより、コンポーネントがre-renderされる

という流れになる。