React Hooks の deps がどのように評価されているか、React Hooks の実装を読んで知る

2021/11/21

React Hooks の deps ってどうやって評価されてるの?

  • React - Introducing Hooks (おさらい)
    • React Hooks によって、関数コンポーネント内で状態や副作用が表現できる。
    • Effect Hook (例えば useEffect) では、関数コンポーネントで副作用が発生する処理が実装出来る。
    • deps(第 2 引数) で 副作用が依存している値を指定することで、副作用処理の実行を制御出来る。

…訳ですが、副作用が依存している deps が更新されたかどうか、をどのように評価しているか知りたくなりました。

ReactFiberHooks

Reactexport している useEffect の実態ReactHooks.js で定義されています。

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}

resolveDispatcher から 現在の dispatcher (ReactCurrentDispatcher) を取得して、useEffect を処理させているようです。 “current” が付く Dispatcher なので、Dispatcher は色々状態を持つようですね。

横着して grep した結果、 ReactCurrentDispatcher.currentDispatcher オブジェクトを代入しているのは ReactFiberHooks っぽい。

ReactFiberHooks には 各 Dispatcher の実装もありました。

Dispatcher の名前から、React の描画のライフサイクルに従って、dispatcher の実装が切り替わることが分かります。

useEffect に話を戻して、各 Dispatcher が実装している useEffect の実装を確認します。

HooksDispatcherOnMount

const HooksDispatcherOnMount: Dispatcher = {
  // ...
  useEffect: mountEffect,
  // ...
};

mountEffect

Development ビルド用の あれ これ をした後、mountEffectImpl に処理を渡しています。

mountEffectImpl

mount は deps が変化する前の状態なので、deps の比較もしていませんね。これは React の使用者としての API の理解と一致してる。

HooksDispatcherOnUpdate, HooksDispatcherOnRerender

const HooksDispatcherOnUpdate: Dispatcher = {
  // ...
  useEffect: updateEffect,
  // ...
};

updateEffect

  • mountEffect と同様、Development ビルド用の あれこれ をして、updateEffectImpl に処理を渡しています。

updateEffectImpl

比較に使用している areHookInputsEqual が 副作用関数を実行する/実行しないを決定するロジックのようですね。

ContextOnlyDispatcher

const ContextOnlyDispatcher: Dispatcher = {
  // ...
  useEffect: throwInvalidHookError,
  // ...
};

throwInvalidHookError

不正な React Hooks の呼び出しをお知らせする だけ。

Object.is

updateEffectImpl が実行している areHookInputsEqual は、Development ビルド用の あれ これ それ をした後に、 2 つの deps の 配列要素毎is() で値の比較をして、1 つでも異なるものがあれば deps がが更新されていると判断されるようです。

React が実装している is()Object.is の polyfill のようなので、MDN を見ます。

Object.is() は 2 つの値が同一値であるかどうかを判定します。2 つの値が以下の規則の一つに当てはまる場合に同一となります。

  • どちらも undefined
  • どちらも null
  • どちらも true かどちらも false
  • どちらも同じ文字からなる同じ長さの文字列
  • どちらも同じオブジェクト
  • どちらも数で、どちらも +0、どちらも -0、どちらも NaN、あるいはどちらもゼロ以外で NaN でなく、同じ数値を持つ

余談: Object.is, === は(もちろん == も) 比較結果が違う(等価性の比較と同一性) 。

結論

deps の各要素は Object.is同一値 かどうかをチェックされる。

Profile picture
LLer and programmer.
@T28_tatsuya