【実務・中級編】 useLayoutEffectとuseEffectの実行タイミングの差異 – React実践ガイド

Reactの「同期」と「非同期」の境界線:useEffectとuseLayoutEffectを使いこなす極意

Reactで開発をしていると、避けては通れないのが「画面がチカッと瞬きする(フリッカー)」現象だ。CSSで頑張っても直らない、あの忌々しいチラつき。もし君がそれを「Reactあるある」で片付けているなら、今日でその認識を捨ててほしい。

今回は、Reactにおける副作用の制御、特に `useEffect` と `useLayoutEffect` の決定的な違いについて、ブラウザの裏側で何が起きているのかという「泥臭い現場の知識」を共有する。

—

ブラウザの描画パイプラインを知る

まず、ブラウザが画面を表示するまでの流れを整理しよう。

1. JavaScriptの実行: ReactがDOMを更新する。
2. ブラウザによる計算: スタイル計算とレイアウト(Reflow)が行われる。
3. ペイント: 画面が描画される。

ここで重要なのは、`useEffect` は「ペイントの後」に実行されるということだ。Reactは画面を描画し、ユーザーにコンテンツを見せた「後」に処理を走らせる。これこそが、パフォーマンスを維持するためのReactの知恵だ。

一方で、`useLayoutEffect` は 「DOMが更新された直後、かつペイントの前」 に同期的に実行される。

なぜ `useLayoutEffect` が必要なのか?

「ペイントの前に実行される」ということは、ブラウザが画面を表示する前に、ReactがDOMの状態を強制的に書き換えることができることを意味する。

もし `useEffect` でDOMのサイズを取得してスタイルを書き換えると、どうなるか。
1. Reactが一度レンダリングして画面に出す(ここで不完全な状態が見える)。
2. `useEffect` が動く。
3. DOMを操作する。
4. ブラウザが再計算し、画面が更新される。

この「2回の描画」の隙間に、あのチラつきが発生するんだ。これを防ぐために `useLayoutEffect` を使う。

実践:チラつきを封じ込めるコード

例えば、ツールチップやモーダルの位置を、DOMのサイズに基づいて動的に計算するようなケースを想像してほしい。

import React, { useState, useLayoutEffect, useRef } from ‘react’;

const Tooltip = () => {
const [position, setPosition] = useState(0);
const elementRef = useRef(null);

// useEffectだと画面描画後に計算するため、一瞬ズレてから位置が修正される
// useLayoutEffectなら描画前に同期的に計算するため、ユーザーには最初から正しい位置に見える
useLayoutEffect(() => {
if (elementRef.current) {
// DOMの実際の幅や高さを取得して、位置を再計算する
const { offsetWidth } = elementRef.current;

// 例えば、親要素の中央に配置するための補正処理など
const calculatedPosition = window.innerWidth / 2 – offsetWidth / 2;
setPosition(calculatedPosition);
}
}, []); // 初回マウント時のみ実行

return (


私は計算された位置に表示されます!

);
};

現場で使い分けるための鉄則

「とりあえず `useLayoutEffect` を使えばいいじゃん」と思った君、それは危険だ。

`useLayoutEffect` は同期的に実行されるため、その中で重い処理を走らせると、ブラウザの描画そのものをブロックしてしまう。 結果として、アプリ全体が重く、クリックに対する反応が鈍い「モッサリしたUI」が出来上がる。

以下の基準を心に刻んでおいてくれ。

  • 基本は `useEffect`: ほとんどの副作用(API通信、イベントリスナーの登録、ログ送信など)はこれで十分だ。パフォーマンスへの影響を最小限に抑えるのがエンジニアの矜持だよ。
  • 例外は `useLayoutEffect`: DOMのサイズ、位置、スクロール位置など、「画面が描画される前に確定していなければならない情報」を使って、DOMを書き換える時だけこれを使う。

まとめ

Reactは「UIと状態の同期」を簡潔に書ける素晴らしいライブラリだが、その裏側にあるブラウザのレンダリングの仕組みを無視すると、必ずどこかで綻びが出る。

「なぜこのフックを使うのか?」という問いに対して、「ブラウザのペイント処理をブロックせず、しかしチラつきを回避するために、このタイミングで同期的に実行する必要があるからだ」 と、自信を持って答えられるようになってほしい。

現場でバグに悩んだとき、この「同期と非同期の境界線」を思い出せば、必ず解決の糸口が見えるはずだ。頑張ろうぜ。

コメント

タイトルとURLをコピーしました