【React】useLayoutEffectとは?「特徴」や「useEffectとの違い」を解説!

Reactを使っていると頻繁に登場するフックuseEffect

しかし、似たような名前で「どっちを使えばいいの?」と迷いやすいのがuseLayoutEffectです。

名前は似ていますが、両者には実行されるタイミングに明確な違いがあります。

この記事では『useLayoutEffect』について、以下の内容を初心者にもわかりやすく解説します。

  • useLayoutEffectとは?
  • useLayoutEffectの特徴
  • useEffectuseLayoutEffectの違い

useLayoutEffectとは?

useLayoutEffectは、ReactがDOMを更新した直後、ブラウザが画面を描画する前に同期的に実行されるフックです。

使い方の構文自体はuseEffectとほとんど変わりません。ただし動作のタイミングが異なるため、以下のような場面に適しています。

  • ユーザーに画面が表示される前に処理を実行したいとき
  • 要素のサイズや位置など、正確なレイアウト計測を行いたいとき
  • スタイルの調整を「ちらつきなく」行いたいとき

サンプルコード(幅の計測)

import { useLayoutEffect, useState } from "react";

function App() {
  const [width, setWidth] = useState(0);

  useLayoutEffect(() => {
    // DOMが配置された直後に幅を測定
    const element = document.getElementById("box");
    if (element) {
      setWidth(element.offsetWidth);
    }
  }, []);

  return (
    <div>
      <div id="box" style={{ width: "200px", border: "1px solid black" }}>
        サンプルボックス
      </div>
      <p>幅: {width}px</p>
    </div>
  );
}

export default App;

このように「描画前に正確なレイアウト計測をしたい」ときに使われます。

useLayoutEffectの特徴

useLayoutEffectの特徴を以下に示します。

  • 同期的に実行される
    • 処理が完了するまでブラウザの描画が止まる。
  • ちらつきを防げる
    • DOMのレイアウトやスタイルを描画前に変更できるため、一瞬表示が変わる「フラッシュ」を防止できる。
  • DOMのレイアウト操作や計測に適している
    • 要素の幅や高さ、スクロール位置などを正確に扱いたい場面に有効。
  • パフォーマンスへの影響に注意
    • 描画がブロックされるため、重い処理を実行すると体感速度が遅くなり、ユーザー体験を損ねる可能性がある。

useEffectとuseLayoutEffectの違い

useEffectuseLayoutEffectの一番大きな違いは副作用(Effect)の実行タイミングにあります。このタイミングの違いが、「ちらつきが起きるかどうか」「パフォーマンスにどう影響するか」に直結します。

項目useEffectuseLayoutEffect
実行タイミングコンポーネントのレンダリングが完了し、ブラウザが画面を描画した「」に非同期的に実行される。Reactが仮想DOMを実際のDOMに反映した直後、ブラウザが画面に描画する「」に同期的に実行される。
主な用途・APIからのデータ取得
・イベントリスナーの登録・解除
・ログ出力や外部サービスへの通信
・要素の幅・高さ・位置などのレイアウト計測
・スタイルを描画前に調整して「最初から正しい見た目」にする
メリット描画を妨げないためパフォーマンスに優れるちらつきを防げる(画面が最初から整った状態)
デメリットDOMがすでにユーザーに表示された後に動くため、要素のサイズ調整やスタイル変更を行うと「一瞬ズレてから修正される」ようなちらつき(フラッシュ現象)が発生することがある。描画が「一時停止」して処理が完了するまで待つため、ここに重い処理を入れるとページ全体の描画が遅れ、パフォーマンスを悪化させる可能性がある。

次の例でuseEffectuseLayoutEffectの違いを体感できます。

useEffectの場合(ちらつきが発生)

import { useEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (count === 0) {
      setCount(Math.floor(Math.random() * 100) + 1);
    }
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(0)}>0にリセット</button>
    </div>
  );
}

export default App;

挙動の流れ

  1. 初期レンダリング
    • countの初期値は0なので、仮想DOM上の<p>{count}</p>には「0」が設定される。
  2. ブラウザの描画処理
    • Reactが生成したDOMがブラウザに渡され、ユーザーの画面に「0」が一度表示される。
  3. useEffectの発火
    • 描画が完了した後にuseEffectが実行され、setCountによってランダムな数値がセットされる。
  4. 再レンダリング
    • Reactが再レンダリングを行い、仮想DOM上の<p>がランダムな数値に置き換えられる。
  5. 再描画
    • ブラウザが更新後のDOMを描画し、画面上の「0」がランダムな数値に切り替わる。

このとき、一瞬だけ0 → ランダムな数値に切り替わるため、ユーザーの目には「ちらつき(フラッシュ現象)」として映る。

同様に、ボタンを押してsetCount(0)を実行した場合も、

  • 一瞬<p>0が表示される
  • すぐにランダムな数値へ切り替わる

という流れになり、やはりちらつきが起きます。

この「ちらつき」はuseEffectがブラウザの描画完了後に実行されるために発生しています。

useLayoutEffectの場合(ちらつきなし)

import { useLayoutEffect, useState } from "react";

function App() {
  const [count, setCount] = useState(0);

  useLayoutEffect(() => {
    if (count === 0) {
      setCount(Math.floor(Math.random() * 100) + 1);
    }
  }, [count]);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(0)}>0にリセット</button>
    </div>
  );
}
export default App;

挙動の流れ

  1. 初期レンダリング
    • countの初期値は0なので、仮想DOM上の<p>{count}</p>には「0」が設定される。
  2. useLayoutEffectの発火
    • Reactが実際のDOMを更新した直後、ブラウザが描画を行う前に同期的にuseLayoutEffectが実行される。
    • ここでsetCountによってランダムな数値がセットされる。
  3. 再レンダリング
    • Reactが即座に再レンダリングを行い、仮想DOM上の<p>が「ランダムな数値」に置き換わる。
  4. ブラウザの描画処理
    • ブラウザが画面を描画する。この時、すでに<p>はランダム値に更新済みなので、ユーザーが目にするのは最初からランダムな数値

つまり、ブラウザに実際の描画が渡る前に値が更新されるので、ユーザーにはちらつきが一切見えない

本記事のまとめ

この記事では『useLayoutEffect』について、以下の内容を説明しました。

  • useEffect: 描画後に実行(非同期)。主にデータ取得やイベント登録向き。
  • useLayoutEffect: 描画前に実行(同期)。DOM 計測やスタイル調整に使う。

基本的にuseEffectを使い、「DOMを扱う処理でちらつきが気になるとき」だけuseLayoutEffectに切り替えるのがベストです。

スポンサーリンク