SWRのコードリーディング
コードを読んで気になった点をメモ
keyの比較
keyの比較は、stableHash
という関数により、深い比較を行うようになっている。実装は下記。
深い比較を行うために、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がトリガされないようになっている。
キャッシュの持ち方
SWRが内部で持っているキャッシュは、cache providerに保存される。
デフォルトでは、Map
がキャッシュプロバイダとして使われる。
キャッシュの中身
providerに保持されるキャッシュは、下記のようにdata, error, isValidating, isLoadingからなるState。
export type State<Data = any, Error = any> = {
data?: Data
error?: Error
isValidating?: boolean
isLoading?: boolean
}
SWRGlobalState
キャッシュプロバイダは、SWRGlobalState
というものに保存される。
SWRGlobalState
は、SWR全体で共有するWeakMap。
SWRGlobalState
は、キャッシュプロバイダをキーとして、下記のようなものを保持する。
[
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
]
下記それぞれの詳細(読み取れる範囲)。
- EVENT_REVALIDATORS
- keyごとに保持する。
- フォーカスイベントなど、revalidateを引き起こしうるイベントの発生時に呼ばれるrevalidate関数。コンポーネントのマウント時及びkeyの変更時に、
SWRGlobalState
に登録される。 - https://github.com/vercel/swr/blob/main/core/use-swr.ts#L534
- https://github.com/vercel/swr/blob/main/core/use-swr.ts#L571
- MUTATION
- keyごとに保持する。
- mutationの開始と終了のタイムスタンプを保持する。mutationのrace conditionの対応に使われている
- https://github.com/vercel/swr/blob/main/core/use-swr.ts#L396
- FETCH
- keyごとに保持する。
- 現在進行中のfetchを記録しておくもの。fetchを開始した時に、fetcherが返すPromiseと、fetch開始時刻を記録する。
- https://github.com/vercel/swr/blob/main/core/use-swr.ts#L361
- これもrace conditionの対応に使われている。fetchが完了したとき、他の新しいfetchがすでに走っていたら、fetchの結果を破棄する
- https://github.com/vercel/swr/blob/main/core/use-swr.ts#L378
- PRELOAD
- 飛ばす
- ScopedMutator
- Setter
- providerの指定のkeyにキャッシュをセットする。また、後述のSubscriberで、そのkeyに対してcallbackが登録されていたらそれを発火させる
- https://github.com/vercel/swr/blob/main/_internal/utils/cache.ts#L64
- Subscriber
- 指定のkeyのキャッシュが変化したら発火するcallbackを登録する
- https://github.com/vercel/swr/blob/main/_internal/utils/cache.ts#L54
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
に入っているプロパティのみをチェックしている。- https://github.com/vercel/swr/blob/main/core/use-swr.ts#L109
-
stateDependencies
は、単にuseRef
で作っているオブジェクト。
-
useSWR
の返り値のプロパティがなんらかの形でアクセスされると、get関数が走るようになっており、そこでstateDependencies
にプロパティが追加される形になっている。
つまり、例えば、 useSWR
の返り値のisValidating
になんらかの形でアクセスすると、
- get関数の中で
stateDependencies
にisValidating
が追加される - その後、
isValidating
が変化すると、useSyncExternalStore
に登録しておいた処理の中で、isEqual
がfalseになり、スナップショット取得処理が実行される - これにより、コンポーネントがre-renderされる
という流れになる。