対象読者: 「useEffectより早く発火するやつでしょ?」くらいのふんわりとした理解をしている方
useLayoutEffect とは何か?
まずズバリ結論から言うと、「ブラウザが要素をレイアウトしたあと、画面に描画する前に同期的にJSを実行するためのフック」です。
さっぱりわからないと思うので順を追って説明します。
ブラウザのレンダリングの流れ
useLayoutEffect の動作を知るには、ブラウザのレンダリング処理について知らなければなりません。
ブラウザのレンダリングはおおむね次のような流れです。
- DOM が変更される
- レンダリングツリーを構築
- レンダリングツリーをもとにそれぞれの要素の位置と大きさを計算(Layout)
- 描画内容を計算(Paint)
- Paint した内容を合成(Composite)
- ユーザーの画面に実際に反映
このように、ユーザーに見えるようになるまでに複数の工程があります。
useLayoutEffect はいつ実行されるか?
useLayoutEffect は React がコンポーネントをレンダリングして、それを元にDOM を変更して、ブラウザが Layout したあとに実行されます。つまり要素の大きさと位置の情報は取れますがユーザーには見えていない段階です。
(引用: hook-flow)
useLayoutEffect がチラつき抑制になるのはなぜ?
よく useEffect だとチラつくから useLayoutEffect を使うという説明があります。ここまでの説明でなぜチラつきを抑えられるかも説明ができます。
もし、useLayoutEffect をした結果 DOM が変更になったらどうなるでしょうか?答えは Paint が行われず、Layout から再実行される です。ユーザーには useLayoutEffect が実行されたあとの画面しか見えません。そのため変更前の画面はユーザーに見えません。
useEffect は画面に反映された後に実行され、再度 Layout → Paint が行われるためチラつくのです。
useLayoutEffect が同期的とはどういうことか?
Paint は Layout のあと、JS の実行が止まり次第走ります。つまり同期的処理は終わるまで Paint をブロッキングします。逆に言えば非同期的処理は Paint をブロッキングしません。そのため同期処的理でなければなりません。
参考
https://leap-in.com/ja/lets-learn-how-to-browser-works/ https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-mostly-complete-guide-to-react-rendering-behavior/#render-behavior-edge-cases https://ja.reactjs.org/docs/hooks-reference.html#uselayouteffect