Share
Embed
React Hooksの実装を探検してみる / Explore the React Hooks implementation
193Otani
July 28, 2022
Transcript
21 slides
-
自己紹介 # 経歴 • 2020年4月にカオナビに新卒入社 • 2年目からフロントエンドエンジニアとして本格的にスタート • 現在は機能拡充を中心としたチームにて活動中 •
趣味はダーツ、ゲーム、モータースポーツ ◦ 今年はF1がアツいです -
目指すこと/目指さないこと # 目指すこと • Hooksが呼び出されたときReactがなにをしているのかをざっくり理解する • 調査の過程で出てきたReact内部の構造、キーワードを知ってもらう # 目指さないこと •
React / React Hooksの動作原理を完璧に理解する • 登場するキーワードについて完全に理解する -
Reactとは? • .FUB چ'BDFCPPL ͱίϛϡχςΟʹΑͬͯ։ൃ͞Ε͍ͯΔɺ6*ߏஙͷͨΊ ͷKBWBTDSJQUϥΠϒϥϦ (JUIVC GBDFCPPLSFBDU •
ҎԼͷͭΛίϯηϓτͱͯ͠։ൃ͞Ε͍ͯΔ ◦ એݴతͳ7JFX ◦ ίϯϙʔωϯτϕʔε ◦ Ұֶश͢ΕɺͲ͜Ͱ͑Δ • 8FCϒϥβ͚ͷ6*։ൃʹՃ͑ɺ3FBDU/BUJWFΛ༻͍ͨϞόΠϧΞϓϦ ͷ։ൃʹར༻Ͱ͖Δ -
React Hooksとは? • React 16.8から追加されたReactの新機能群 • 関数コンポーネント内でReactのstateやライフサイクルメソッドを使用する ことができる(クラスコンポーネント内では機能しない) • ビルトインですぐに利用できるHooksが用意されている
◦ useState, useEffect, useMemo etc… -
useEffectとは? • 関数コンポーネント内で副作用を実行することができる ◦ 副作用とは? - ある処理を実行した結果他の処理に対して影響を伴うようなもの ◦ stateの値を更新する、APIの値を取得するなど •
useEffectに渡された副作用関数は画面のレンダリング後に発火される • クラスコンポーネントにおける以下のライフサイクルメソッドの代替機能 ◦ componentDidMount, componentDidUpdate, componentWillUnmount -
useEffectの本体 • react/src/ReactHooks.js • ReactCurrentDispatcher.current.useEffect(create, deps)を返している ◦ create -> useEffectで実行させたい副作用関数
◦ deps -> 副作用関数が依存している変数の配列 // @flow export function useEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { const dispatcher = resolveDispatcher(); return dispatcher.useEffect(create, deps); } // @flow import ReactCurrentDispatcher from './ReactCurrentDispatcher'; function resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; … // コメント省略 return ((dispatcher: any): Dispatcher); } -
ReactCurrentDispatcherを見る • ReactCurrentDispatcher.js • currentの中身空っぽいんだけど? ◦ Dispatcher型がセットされるようだが...? ◦ react-reconcilerってなんだ? //
@flow import type {Dispatcher} from 'react- reconciler/src/ReactInternalTypes'; const ReactCurrentDispatcher = { /** * @internal * @type {ReactComponent} */ current: (null: null | Dispatcher), }; export default ReactCurrentDispatcher; -
React-Reconcilerってなに? • Fiberという単位でコンポーネントの状態や更新情報を管理している ◦ Fiberは一つのコンポーネントに対して基本的に1:1で存在する ◦ FiberのTreeを構成し、さらに変更前と変更後の二つのTreeを比較することで、更 新が必要な差分の検出を行っている(Reconciliation) • Reconcilerはこの差分検出処理群
• コアロジックを共有することで、Webアプリやネイティブアプリにおける動 作の一貫性を保っている ◦ ブラウザ開発開発: React + React DOM ◦ モバイル向け開発: React + React Native -
結局、ReactCurrentDispatcher.currentの中身は? • renderWithHooks - ReactFiberHooks.new.js: L.373 • currentの中身はHooksDispatcherOnMount or HooksDispatcherOnUpdate
◦ 各Hookに対応した関数を持つオブジェクト ◦ OnMount: L.2420 ◦ OnUpdate: L. 2427 // ReactFiberHooks.new.js - L.373 … if (__DEV__) { ... } else { ReactCurrentDispatcher.current = current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; } … -
HooksDispatcherOnMount • 各Hookに対応する初期化関数をもっている • OnUpdateの場合は更新関数が割り当てら れている ◦ useEffect: updateEffect const
HooksDispatcherOnMount: Dispatcher = { readContext, useCallback: mountCallback, useContext: readContext, useEffect: mountEffect, useImperativeHandle: mountImperativeHandle, …, useId: mountId, unstable_isNewReconciler: enableNewReconciler, }; -
mountEffect(...) • 特別な処理はなさそう • mounEffectImplに渡しているフラグが気になる // ReactFiberHooks.new.jp - L.1701 function
mountEffect( create: () => (() => void) | void, deps: Array<mixed> | void | null, ): void { if ( __DEV__ && … ) { return mountEffectImpl(...); } else { return mountEffectImpl( PassiveEffect | PassiveStaticEffect, HookPassive, create, deps, ); } } -
mountEffectImpl(...) • mountWorkInProgressHook(...) - L.635 ◦ Effect Hookの状態管理ツリーに現在の状態が保存されたノ ードを追加 ◦
上記ノードが持っている情報ごと • pushEffect(...) - L. 1543 ◦ 現在のeffectの状態を保存したEffectノードを作成 し、Fiberが持っている更新キューに追加 ◦ 保存される情報には依存配列の状態を含む // ReactFiberHooks.new.js - L. 1662 function mountEffectImpl(fiberFlags, hookFlags, create, deps): void { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; currentlyRenderingFiber.flags |= fiberFlags; hook.memoizedState = pushEffect( HookHasEffect | hookFlags, create, undefined, nextDeps, ); } -
2回目以降の動きはどうなるのか? • useEffectは基本的に毎レンダリング呼び出される • useEffectは引数として依存する変数の配列を渡すことができる • 依存配列の中身が変わらない場合はuseEffectの処理は実行されない useEffect(() => {
console.log(effectVal); },[effectVal]); -
updateEffectImplに秘密が... • 入力される依存配列が過去に使用したものと一致するかチェックしている • 一致した場合、flagsの更新をしていない = useEffectの動作と一致している // L. 1674
const prevDeps = prevEffect.deps; if (areHookInputsEqual(nextDeps, prevDeps)) { hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps); return; } } } currentlyRenderingFiber.flags |= fiberFlags; // 副作用を適用するフラグを立てる -
useEffectの実装を追ってみて • എܠʹ͋Δ࣮ͷҙਤ։ൃνʔϜͷߟ͑ํʹ;ΕΔ͜ͱ͕Ͱ͖ͨ • ReconcilerɺFiberͷಋೖཧ༝ͳͲ • ίʔυΛඈͼඈͼͰ͏ඞཁ͕͋Δ+FiberΛར༻ͨ͠ཧํ๏ʹෆ׳ΕͰί ʔυΛͬͯཧղ͢ΔͷΘΓͱେม • React
Hooksଞʹ࣮͞Ε͍ͯΔͷͰɺڵຯ͕͋ΔํͬͯΈͯʁ ◦ ࣗuseEffectͷ࣮͏ͷͰർΕ·ͨ͠... -
参考 • React-Fiber-Architecture • Reconciliation - React • An Introduction
to React Fiber - The Algorithm Behind React • React Fiberとその周りについて調べた