useLayoutEffect 完全入門【React】描画前に実行するエフェクト

ReactのuseLayoutEffectとは何か、useEffectとの違いや使いどころを初心者向けに解説。ツールチップの位置計算など、レイアウト測定が必要な場面での使い方を紹介します。

#react#hooks#uselayouteffect#javascript#frontend

useEffect を使っていると、まれに「画面がちらつく」「要素の位置が一瞬ずれる」という問題に遭遇することがあります。そんなときの解決策として用意されているのが useLayoutEffect です。

ほとんどのケースでは useEffect で十分ですが、DOM の測定結果をもとに再描画が必要な場面では useLayoutEffect が適しています。

この記事でわかること:

  • useLayoutEffect が必要な場面
  • useEffect との実行タイミングの違い
  • ツールチップなどのレイアウト計算での使い方
  • サーバーサイドレンダリングでの注意点

useLayoutEffect とは?

useLayoutEffect は、ブラウザが画面を再描画する前に実行されるエフェクトフックです。

useLayoutEffect(setup関数, 依存配列);

useEffect と API はまったく同じです。違うのは実行タイミングだけです。

フック実行タイミング
useEffectブラウザが画面を描画した
useLayoutEffectブラウザが画面を描画する

useEffect との違いを図で理解する

【useEffect の流れ】
① React がDOM を更新する
② ブラウザが画面を描画する  ← ユーザーに見える
③ useEffect が実行される

【useLayoutEffect の流れ】
① React が DOM を更新する
② useLayoutEffect が実行される  ← 描画前に処理!
③ ブラウザが画面を描画する  ← ユーザーに見える

useLayoutEffect は描画をブロックして先に処理するため、「一瞬ちらつく」ような問題が起きないのが特徴です。

実用例:ツールチップの位置を動的に計算する

ツールチップは表示するためのスペースが足りないとき、位置を上下に切り替える必要があります。これを useEffect でやると位置が一瞬ずれることがありますが、useLayoutEffect を使えばスムーズに表示できます。

import { useRef, useState, useLayoutEffect } from "react";
 
function Tooltip({ children, text }) {
  const tooltipRef = useRef(null);
  const [position, setPosition] = useState("above"); // 初期位置は上
 
  useLayoutEffect(() => {
    const tooltip = tooltipRef.current;
    const rect = tooltip.getBoundingClientRect();
 
    // ツールチップが画面上部にはみ出す場合は下に表示
    if (rect.top < 0) {
      setPosition("below");
    } else {
      setPosition("above");
    }
  });
 
  return (
    <div>
      <div
        ref={tooltipRef}
        style={{
          position: "absolute",
          top: position === "above" ? "-40px" : "100%",
        }}
      >
        {text}
      </div>
      {children}
    </div>
  );
}

このコードでは:

  1. React が DOM を更新する
  2. useLayoutEffect が実行され、ツールチップの位置を測定・調整する
  3. 調整済みの状態でブラウザが描画する

ユーザーには「最初から正しい位置に表示されたツールチップ」として見えます。

パフォーマンスへの影響

useLayoutEffect は描画をブロックします。処理が重いとその分だけ画面の表示が遅れてしまいます。

// ❌ NG:重い処理を useLayoutEffect で書かない
useLayoutEffect(() => {
  // 1万件のデータを処理する、など重い処理
  processHeavyData();
}, [data]);
 
// ✅ OK:軽いレイアウト測定に限定する
useLayoutEffect(() => {
  const height = ref.current.offsetHeight;
  setHeight(height);
}, []);

原則として useEffect を使い、ちらつき問題が起きた場合にのみ useLayoutEffect に切り替えるのがベストプラクティスです。

サーバーサイドレンダリングでの注意

useLayoutEffect はサーバー(Node.js)では実行されません。そのため、Next.js などの SSR 環境で使うと以下の警告が表示されることがあります:

Warning: useLayoutEffect does nothing on the server...

SSR で使う場合は、クライアントのみでマウントされるようにガードするか、useEffect を使うことを検討してください。

// SSR 対応のパターン
const [isMounted, setIsMounted] = useState(false);
 
useEffect(() => {
  setIsMounted(true);
}, []);
 
// isMounted のときだけレイアウト依存の処理を行う

まとめ

  • useLayoutEffectブラウザ描画前に実行されるエフェクト
  • useEffect と API は同じで、実行タイミングだけが違う
  • ツールチップの位置計算など、DOM 測定→再描画が必要な場面で使う
  • 描画をブロックするため、重い処理には使わない
  • SSR 環境では実行されないことに注意
  • 基本は useEffect を使い、ちらつきが起きたら useLayoutEffect を検討

関連記事