Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Hook Form はどのように再レンダリングを最適化しているのか?

React Hook Form はどのように再レンダリングを最適化しているのか?

【READYFOR×コミューン】業務に活かせるReact LT & 座談会
https://readyfor.connpass.com/event/224586/

Kotaro Sugawara

October 29, 2021
Tweet

More Decks by Kotaro Sugawara

Other Decks in Technology

Transcript

  1. 2 自己紹介 Kotaro Sugawara @kotarella1110 • エンジニアリング本部/システム基盤部 • フロントエンドエンジニア •

    2020年11月入社(もう少しで入社して一年 🎉) • 岩手県在住のリモートワーカー • React Hook Form のメンバー #ReactLT
  2. 3 本日お話しすること • React Hook Form とは? • React のパフォーマンス最適化

    • React Hook Form はどのように再レンダリングを最適化しているのか? #ReactLT
  3. 7 React Hook Form とは? 特徴 • パフォーマンス、UX、DX を念頭に置いて構築 •

    非制御フォームバリデーション • UI ライブラリと簡単に統合可能(Controlled Components もサポート) • パッケージのサイズが小さく、依存関係がない • HTML 標準のバリデーション(Constraint Validation API) • スキーマベースのバリデーションをサポート(Yup、Zod、Superstruct など) #ReactLT
  4. 8 React Hook Form とは? 特徴 • パフォーマンス、UX、DX を念頭に置いて構築 •

    非制御フォームバリデーション • UI ライブラリと簡単に統合可能(Controlled Components もサポート) • パッケージのサイズが小さく、依存関係がない • HTML 標準のバリデーション(Constraint Validation API) • スキーマベースのバリデーションをサポート(Yup、Zod、Superstruct など) #ReactLT
  5. 11 React のパフォーマンス最適化 パフォーマンスの最適化手法 • useCallback/useMemo による関数と値のメモ化 ◦ 再レンダリング・値の不要な再計算をスキップ •

    React.memo によるコンポーネントのメモ化 ◦ useCallback/useMemo と併用することで、コンポーネントの再レンダリングを スキップ • Context の使用 ◦ Provider と Consumer(Context から値を取得するコンポーネント)間の中間コ ンポーネントの再レンダリングをスキップ #ReactLT
  6. 12 React のパフォーマンス最適化 Context の分割について Provider 配下の全ての Consumer は、Provider の

    value プロパティが変 更されるたびに再レンダリングが発生 します。 これは機能的に問題はありませんが、 不要な再レンダリングが発生してしまう ケースがあります。 Provider Consumer Consumer #ReactLT
  7. 13 Context の分割について React のパフォーマンス最適化 例えば右の図のように、状態(可変)と それを更新する関数(不変)を value に 渡しているケースです。ConsumerA

    で は Context から state のみを取得し、 ConsumerB では setState のみを取 得します。 value={{ state, setState }} Provider ConsumerB ConsumerA state setState #ReactLT
  8. 14 Context の分割について React のパフォーマンス最適化 setState が呼び出される度に Provider では state

    が更新され value プロパティに変更が発生します。 value={{ state, setState }} Provider ConsumerB ConsumerA state setState state setState(value) の呼び出し #ReactLT
  9. 15 Context の分割について React のパフォーマンス最適化 value プロパティに変更があるため、 state を Context

    から取得している ConsumerA だけでなく ConsumerB も 再レンダリングが発生してしまいます。 value={{ state, setState }} Provider ConsumerB ConsumerA state setState state setState(value) の呼び出し #ReactLT
  10. 16 Context の分割について React のパフォーマンス最適化 この不要な再レンダリングを排除するため には Context を分割する必要があります。 右の図のように分割することで、

    ProviderB の value プロパティは不変にな るため、ConsumerB は state が更新され ても再レンダリングが発生しなくなります。 value={{ setState }} ProviderB ConsumerB ConsumerA value={{ state }} state setState ProviderA setState(value) の呼び出し state #ReactLT
  11. 17 Context の分割について React のパフォーマンス最適化 とはいえ、Consumer 側で再レンダリングを細かく制御したい場合など Context を単純に分割できないケースがあります。 例えば、Redux

    の useSelector のように、selector 関数が返す値に更新が あった場合のみ再レンダリングを行うといったことを Context のみで実現するこ とは難しいです。 #ReactLT
  12. 20 Subscription ベースの状態管理 Subscription ベースの状態管理を採用 • React Hook Form は再レンダリングを細かく制御するために

    Redux や Recoil などと同じように Subscription ベースの状態管理を採用 • これにより、useWatch や useFormState フックはそれらを使用するコン ポーネントレベルで再レンダリングを分離することを可能にしている #ReactLT
  13. 21 Subscription ベースの状態管理 Subscription ベースの状態管理とは? • コンポーネントで値の変更をサブスクライブし、 受け取った値に応じて状態を更新する • Provider

    での状態の更新を避けることができ、 サブスクライブしているコンポーネントでのみ再 レンダリングが発生する • 値に応じて細かい再レンダリングの制御が可能 Provider ConsumerB ConsumerA value state 値の変更通知 値の変更をサブスクライブ 受け取った値に応じて状態を更新 #ReactLT
  14. 22 Subscription ベースの状態管理 実現方法 • 値の変更を通知する仕組みと値の変更をサブ スクライブ(購読)して何かしら処理する仕組み さえあれば Subscription ベースの状態管理を

    実現できる • React Hook Form ではその仕組みとして RxJS の Subject の簡易的な実装をしている • Subject は React Hook Form が提供する control オブジェクトに含まれる subject.subscribe(value => { setState(value) }) subject.next(value) value={{ subject }} Provider ConsumerB ConsumerA value subject subject state 値の変更通知 値の変更をサブスクライブ 受け取った値に応じて状態を更新 #ReactLT
  15. 23 Subscription ベースの状態管理 実装例 • Subscription ベースで状態を管理する useCountState フックの 実装例

    ◦ 修正前 ▪ https://codesandbox.io/s/subscription-based-usecountstate-before-9spnk ◦ 修正後 ▪ https://codesandbox.io/s/subscription-based-usecountstate-after-b4eef • React Hook Form の watch と useWatch の簡易的な実装例 ◦ https://codesandbox.io/s/react-hook-form-watch-usewatch-implementation-u1r si #ReactLT
  16. 25 コンポーネントで使用されている状態のみを更新 React Hook Form の formState について • React

    Hook Form が提供する formState というオブジェクトは isDirty 、 isSubmitting、errors などのフォームに関する状態が含まれている • React Hook Form は formState の内、コンポーネントで使用されている状 態のみを更新して再レンダリングを発生させている #ReactLT
  17. 31 コンポーネントで使用されている状態のみを更新 実装例 • コンポーネントで使用されている状態のみを更新する useAsyncフックの実 装例 ◦ 修正前 ▪

    https://codesandbox.io/s/dependency-collection-useasync-before-5epsq ◦ 修正後 ▪ https://codesandbox.io/s/dependency-collection-useasync-after-30o24 #ReactLT
  18. 32 まとめ • React Hook Form においてパフォーマンスは重要であり、不要な再レンダ リングを排除することは主要な目標の一つ • React

    は useCallback・useMemo・React.memo・Context の使用 ・Context の分割によりパフォーマンスを最適化することができる • React Hook Form はSubscription ベースの状態管理を採用し、コンポー ネントで使用されている状態のみを更新することにより再レンダリングを最 適化している #ReactLT