Slide 1

Slide 1 text

1 React Hook Form はどのように再レンダリングを最適化しているのか? 2021/10/29 菅原 弘太郎

Slide 2

Slide 2 text

2 自己紹介 Kotaro Sugawara @kotarella1110 ● エンジニアリング本部/システム基盤部 ● フロントエンドエンジニア ● 2020年11月入社(もう少しで入社して一年 🎉) ● 岩手県在住のリモートワーカー ● React Hook Form のメンバー #ReactLT

Slide 3

Slide 3 text

3 本日お話しすること ● React Hook Form とは? ● React のパフォーマンス最適化 ● React Hook Form はどのように再レンダリングを最適化しているのか? #ReactLT

Slide 4

Slide 4 text

4 React Hook Form とは? #ReactLT

Slide 5

Slide 5 text

5 React Hook Form とは? 非制御コンポーネントをベースに構築されたフォームバリデーション用の React フックライブラリ #ReactLT

Slide 6

Slide 6 text

6 React Hook Form とは? React Hook Form のコード例 #ReactLT

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

9 React のパフォーマンス最適化 #ReactLT

Slide 10

Slide 10 text

10 React のパフォーマンス最適化 React のパフォーマンス最適化において重要なこと 無駄な計算と再レンダリングを抑える #ReactLT

Slide 11

Slide 11 text

11 React のパフォーマンス最適化 パフォーマンスの最適化手法 ● useCallback/useMemo による関数と値のメモ化 ○ 再レンダリング・値の不要な再計算をスキップ ● React.memo によるコンポーネントのメモ化 ○ useCallback/useMemo と併用することで、コンポーネントの再レンダリングを スキップ ● Context の使用 ○ Provider と Consumer(Context から値を取得するコンポーネント)間の中間コ ンポーネントの再レンダリングをスキップ #ReactLT

Slide 12

Slide 12 text

12 React のパフォーマンス最適化 Context の分割について Provider 配下の全ての Consumer は、Provider の value プロパティが変 更されるたびに再レンダリングが発生 します。 これは機能的に問題はありませんが、 不要な再レンダリングが発生してしまう ケースがあります。 Provider Consumer Consumer #ReactLT

Slide 13

Slide 13 text

13 Context の分割について React のパフォーマンス最適化 例えば右の図のように、状態(可変)と それを更新する関数(不変)を value に 渡しているケースです。ConsumerA で は Context から state のみを取得し、 ConsumerB では setState のみを取 得します。 value={{ state, setState }} Provider ConsumerB ConsumerA state setState #ReactLT

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

15 Context の分割について React のパフォーマンス最適化 value プロパティに変更があるため、 state を Context から取得している ConsumerA だけでなく ConsumerB も 再レンダリングが発生してしまいます。 value={{ state, setState }} Provider ConsumerB ConsumerA state setState state setState(value) の呼び出し #ReactLT

Slide 16

Slide 16 text

16 Context の分割について React のパフォーマンス最適化 この不要な再レンダリングを排除するため には Context を分割する必要があります。 右の図のように分割することで、 ProviderB の value プロパティは不変にな るため、ConsumerB は state が更新され ても再レンダリングが発生しなくなります。 value={{ setState }} ProviderB ConsumerB ConsumerA value={{ state }} state setState ProviderA setState(value) の呼び出し state #ReactLT

Slide 17

Slide 17 text

17 Context の分割について React のパフォーマンス最適化 とはいえ、Consumer 側で再レンダリングを細かく制御したい場合など Context を単純に分割できないケースがあります。 例えば、Redux の useSelector のように、selector 関数が返す値に更新が あった場合のみ再レンダリングを行うといったことを Context のみで実現するこ とは難しいです。 #ReactLT

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

19 Subscription ベースの状態管理 #ReactLT

Slide 20

Slide 20 text

20 Subscription ベースの状態管理 Subscription ベースの状態管理を採用 ● React Hook Form は再レンダリングを細かく制御するために Redux や Recoil などと同じように Subscription ベースの状態管理を採用 ● これにより、useWatch や useFormState フックはそれらを使用するコン ポーネントレベルで再レンダリングを分離することを可能にしている #ReactLT

Slide 21

Slide 21 text

21 Subscription ベースの状態管理 Subscription ベースの状態管理とは? ● コンポーネントで値の変更をサブスクライブし、 受け取った値に応じて状態を更新する ● Provider での状態の更新を避けることができ、 サブスクライブしているコンポーネントでのみ再 レンダリングが発生する ● 値に応じて細かい再レンダリングの制御が可能 Provider ConsumerB ConsumerA value state 値の変更通知 値の変更をサブスクライブ 受け取った値に応じて状態を更新 #ReactLT

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

24 コンポーネントで使用されている状態のみを更新 #ReactLT

Slide 25

Slide 25 text

25 コンポーネントで使用されている状態のみを更新 React Hook Form の formState について ● React Hook Form が提供する formState というオブジェクトは isDirty 、 isSubmitting、errors などのフォームに関する状態が含まれている ● React Hook Form は formState の内、コンポーネントで使用されている状 態のみを更新して再レンダリングを発生させている #ReactLT

Slide 26

Slide 26 text

26 こちらは2文字以上入力するとバリデーションエラーが発生するフィールドの例です。 コンポーネントで使用されている状態のみを更新 コンポーネントで使用されている状態のみを更新するとは? #ReactLT

Slide 27

Slide 27 text

27 コンポーネントで使用されている状態のみを更新 コンポーネントで使用されている状態のみを更新するとは? formState の内 isDirty と errors を使用してログを出力してみます。 フィールドで2文字入力すると以下のような3行のログが出力されます。 合計3回レンダリングされています。 #ReactLT

Slide 28

Slide 28 text

28 コンポーネントで使用されている状態のみを更新 コンポーネントで使用されている状態のみを更新するとは? 続いて、formState の内 errors のみを使用するように修正してログを出力してみます。 同じように2文字入力すると、以下のような2行のログが出力されます。 合計3回のレンダリングから2回のレンダリングに減りました。 #ReactLT

Slide 29

Slide 29 text

29 コンポーネントで使用されている状態のみを更新 コンポーネントで使用されている状態のみを更新するとは? これは、コンポーネントで使用している errors のみ更新され、isDirty などの errors 以外の状 態は更新されないためです。 これにより不要な再レンダリングが排除されます。 #ReactLT

Slide 30

Slide 30 text

30 コンポーネントで使用されている状態のみを更新 実現方法 ● Proxy や Object.defineProperty/Object.defineProperties を活用してコン ポーネントで使用されている状態のみを更新し、不要な再レンダリングを排 除することができる ● React Hook Form では Object.defineProperty の getter で状態が使用さ れているかを検知し、使用されている状態のみを更新するようにしている #ReactLT

Slide 31

Slide 31 text

31 コンポーネントで使用されている状態のみを更新 実装例 ● コンポーネントで使用されている状態のみを更新する useAsyncフックの実 装例 ○ 修正前 ■ https://codesandbox.io/s/dependency-collection-useasync-before-5epsq ○ 修正後 ■ https://codesandbox.io/s/dependency-collection-useasync-after-30o24 #ReactLT

Slide 32

Slide 32 text

32 まとめ ● React Hook Form においてパフォーマンスは重要であり、不要な再レンダ リングを排除することは主要な目標の一つ ● React は useCallback・useMemo・React.memo・Context の使用 ・Context の分割によりパフォーマンスを最適化することができる ● React Hook Form はSubscription ベースの状態管理を採用し、コンポー ネントで使用されている状態のみを更新することにより再レンダリングを最 適化している #ReactLT

Slide 33

Slide 33 text

33 パフォーマンスのボトルネックが見つかった場合など、 本日紹介した再レンダリングの最適化を実践してみてください 🙌 #ReactLT

Slide 34

Slide 34 text

34 時間の都合上、実装例の解説は出来なかったため、 次回イベント or テックブログに乞うご期待…!👋 #ReactLT