【入門編】 useEffectとuseLayoutEffectの使い分け – React実践ガイド

皆さん、こんにちは!Reactの海へようこそ!

Reactの世界に足を踏み入れたばかりの皆さん、副作用の制御、特に`useEffect`と`useLayoutEffect`の使い分けについて、ちょっと頭を悩ませていませんか?「なんだか似たような名前だけど、何が違うの?」「いつどっちを使えばいいの?」なんて、モヤモヤしているかもしれませんね。

大丈夫ですよ!心配いりません。今日は、私が皆さんのそのモヤモヤをスッキリさせるお手伝いをさせていただきます。伝説のチーフアーキテクトとして、現場で培った「これぞ!」という知見を、優しく、そして楽しくお伝えしていきますからね。

Reactの「副作用」って、結局何者?

まず、`useEffect`や`useLayoutEffect`の話をする前に、Reactが言うところの「副作用」って何だろう?というところから、軽く触れておきましょう。

Reactは、皆さんが書いたコンポーネントのコードを元に、画面(UI)を「描画」するのがお仕事です。でも、Webの世界は画面表示だけで成り立っているわけではありません。

  • 外部のAPIからデータを取ってくる
  • ブラウザのタイトルを書き換える
  • DOM(Webページの要素そのもの)を直接操作する
  • タイマーをセットする

…これらはすべて、Reactが管理する画面表示とは「別の場所」で起こる出来事ですよね。こういう、コンポーネント自身のレンダリング(描画)とは切り離された、外部とのやり取りや、描画以外の操作のことを、Reactでは「副作用(Side Effect)」と呼んでいます。

そして、この副作用を「いつ」「どうやって」安全に実行するかを制御するのが、`useEffect`や`useLayoutEffect`といったフックの役割なんです。

まずは基本の「useEffect」をおさらい:舞台裏の演出家さん

皆さんが最もよく目にするのは、おそらく`useEffect`でしょう。これは、Reactコンポーネントで副作用を扱うための、いわば「基本のキ」となるフックです。

`useEffect`は、その名の通り「エフェクト(副作用)のために使うフック」なのですが、一番のポイントは、その実行タイミングにあります。

`useEffect`は、Reactがコンポーネントの描画を終えて、その結果を画面に反映しきった「後」に実行されます。

これを、身近な例で考えてみましょう。

想像してみてください。あなたは人気カフェの店長さんです。お店が閉店して、お客様がみんな帰り終えました。さて、ここからが店長さんのお仕事です。

  • 「閉店後」に、翌日の仕込みを始めたり…
  • 「閉店後」に、お店の掃除をしたり…
  • 「閉店後」に、売上を確認したり…

これらはすべて、お客様がお店にいる間(=画面が表示されている間)には行いませんよね?お客様に快適に過ごしてもらうため、そしてお店の営業を邪魔しないために、「お店の営業が終わってから」静かに、裏方で作業を進めます。

`useEffect`も全く同じです。ユーザーが目にする画面表示(UI)を邪魔しないよう、「画面に表示し終わってから」、ひっそりと、しかし確実にその役割を果たしてくれる、まるで「舞台裏の演出家さん」のような存在なんです。

import React, { useEffect, useState } from ‘react’;

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

// useEffectの例:countが変更されたらブラウザのタイトルを変更
useEffect(() => {
// この処理は、コンポーネントが画面に描画された後に実行されます
document.title = `現在のカウント: ${count}回`;
console.log(‘useEffectが実行されました!’); // 実行タイミングを確認

// クリーンアップ関数(オプション):コンポーネントが消えるとき、または依存配列が変更される前に実行
return () => {
console.log(‘useEffectのクリーンアップが実行されました。’);
// 例えば、イベントリスナーを解除するなどの処理を書きます
};
}, [count]); // 依存配列:countが変更されたら再実行

return (

useEffectでタイトル変更のデモ

現在のカウント: {count}

);
}

export default MyComponent;

この例では、`count`が更新されるたびに、ブラウザのタブに表示されるタイトルが変わります。この処理は、ユーザーがボタンを押して画面上の`count`の数字が更新された「後」に実行されるため、ユーザー体験を損なうことはありません。

useLayoutEffectの登場:舞台監督さん、それとも大道具さん?

さて、いよいよ本題の`useLayoutEffect`です。名前が似ているだけに、「これって`useEffect`と何が違うの?」と混乱しやすいポイントですよね。

`useLayoutEffect`も副作用を扱うフックですが、その実行タイミングが`useEffect`とは大きく異なります。

`useLayoutEffect`は、ReactがDOM(Webページの要素の設計図)の更新を終えた「直後」に、しかし、それがまだブラウザの画面に「表示される前」に実行されます。

ちょっと複雑に聞こえるかもしれませんが、先ほどのカフェの例で考えてみましょう。

今度は、お店のレイアウトをガラッと変える大改装をするとします。棚の位置を変えたり、新しい商品を並べたり…。

  • お客様が来る「前」に、全ての棚を完璧な位置に動かし…
  • お客様が来る「前」に、商品を綺麗に陳列し…
  • お客様が来る「前」に、全ての準備を整えます。

お客様がお店に入ってきて、「あれ?棚がグラグラしてる」「商品がまだ段ボールから出てないぞ?」なんて状態を見せられたら、台無しですよね。

`useLayoutEffect`は、まさにこの「お客様に見せる前に、舞台裏で完璧な準備を整える」役割を担います。Webページで言えば、「ユーザーに画面を見せる前に、DOMの状態を完璧に調整する」イメージです。

つまり、`useLayoutEffect`は「舞台監督さん」や「大道具さん」のように、ユーザーの目に触れる前に、舞台(画面)の準備をガッチリと固めてくれるんです。

使い分けの核心:いつuseLayoutEffectを使うべきか?

ここまでで、`useEffect`は「描画後」、`useLayoutEffect`は「DOM更新後、描画前」というタイミングの違いがあることが分かりました。

では、具体的にどんな時に`useLayoutEffect`を選ぶべきなのでしょうか?

キーワードは「DOM操作」と「視覚的なズレ(チラつき)」です。

`useLayoutEffect`を使うべき主なシチュエーションは、以下の通りです。

1. DOM要素の「寸法」を測り、それに基づいて「位置」を調整する場合

  • 例:ある要素の幅や高さを取得して、それに応じて別の要素の配置を微調整する。
  • もし`useEffect`でこれをやると、一度古い位置やサイズで画面に表示されてから、一瞬「ガクッ」と正しい位置に動く「チラつき」が発生する可能性があります。`useLayoutEffect`なら、この「チラつき」を防ぎ、最初から正しい状態で表示できます。

2. アニメーションの「初期状態」を設定する場合

  • 例:要素を最初は非表示にしておいて、すぐにアニメーションを開始するような場合。
  • `useEffect`だと、一瞬だけ要素が表示されてしまい、すぐに非表示になるという「見せたくない瞬間」が発生することがあります。`useLayoutEffect`なら、ユーザーが目にする前に初期状態をセットできるため、スムーズなアニメーションの開始が可能です。

3. スクロール位置を制御する場合

  • 例:ページが読み込まれたときや、特定の要素が追加されたときに、自動的に特定の場所までスクロールさせたい場合。
  • `useEffect`だと、要素が表示された後にスクロールが動くため、ユーザーは一瞬「表示されてから動いた」という感覚を持つかもしれません。`useLayoutEffect`なら、表示される前にスクロール位置を調整できるため、自然な遷移になります。

要するに、「ユーザーが画面を見たときに、一瞬でも変な表示や不自然な動きを見せたくない!」という、視覚的な完成度を求めるDOM操作が必要なときに、`useLayoutEffect`の出番がやってくるわけです。

具体的なコード例で見てみよう!

では、実際にコードでその違いを見てみましょう。ここでは、「ボタンをクリックすると、要素の高さが変わって、それに合わせて下の要素の位置を調整する」というシナリオを考えます。

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

function UseEffectVsUseLayoutEffect() {
const [height, setHeight] = useState(100); // 調整される要素の高さ
const boxRef = useRef(null); // 高さを測るための要素への参照
const adjustedDivRef = useRef(null); // 位置を調整される要素への参照

// — useEffect を使った場合 —
useEffect(() => {
if (boxRef.current && adjustedDivRef.current) {
// boxRefの現在の高さを取得
const currentHeight = boxRef.current.offsetHeight;
console.log(`useEffect: boxRefの高さは ${currentHeight}px です`);

// 取得した高さに基づいて、adjustedDivの位置を調整
// ここで意図的に遅延を挟むことで、useEffectだとチラつく可能性があることを示唆
// 実際には、この遅延がブラウザのレンダリングタイミングと重なってチラつきが発生します
setTimeout(() => {
adjustedDivRef.current.style.marginTop = `${currentHeight + 20}px`;
console.log(‘useEffect: adjustedDivの位置を調整しました。’);
}, 0); // 遅延なしでもブラウザの描画タイミングでチラつく可能性あり
}
}, [height]); // heightが変更されたら再実行

// — useLayoutEffect を使った場合 —
useLayoutEffect(() => {
if (boxRef.current && adjustedDivRef.current) {
// boxRefの現在の高さを取得
const currentHeight = boxRef.current.offsetHeight;
console.log(`useLayoutEffect: boxRefの高さは ${currentHeight}px です`);

// 取得した高さに基づいて、adjustedDivの位置を調整
// useLayoutEffectは描画前に同期的に実行されるため、チラつきが起きにくい
adjustedDivRef.current.style.marginTop = `${currentHeight + 20}px`;
console.log(‘useLayoutEffect: adjustedDivの位置を調整しました。’);
}
}, [height]); // heightが変更されたら再実行

const toggleHeight = () => {
setHeight(prevHeight => (prevHeight === 100 ? 200 : 100)); // 高さを切り替え
};

return (

useEffect と useLayoutEffect の違いを比較

下の「高さが変わるボックス」の高さが変わると、
「位置調整される要素」の位置が連動して変わります。
コンソールログと表示の動きに注目してください。


useEffectを使った場合:


高さが変わるボックス ({height}px)

位置調整される要素 (useEffect)

useLayoutEffectを使った場合:

{/ useLayoutEffect用にもう一組用意して比較 /}


高さが変わるボックス ({height}px)

位置調整される要素 (useLayoutEffect)

);
}

export default UseEffectVsUseLayoutEffect;

【解説】

このコードは、同じ要素(`boxRef`で参照されるボックス)の高さが変化したときに、別の要素(`adjustedDivRef`で参照される要素)の`marginTop`を調整する処理を、`useEffect`と`useLayoutEffect`それぞれで書いています。

  • `useEffect`を使った場合、ボックスの高さが変更された後、Reactが画面を「描画し終えてから」`useEffect`が実行されます。そのため、もしブラウザの描画タイミングと`useEffect`内のDOM操作が重なると、一瞬だけ古い位置で表示されてから、ガクッと新しい位置に動くような「チラつき」が見える可能性があります。特に、PCのスペックが低い環境や、処理が重い場合により顕著になります。
  • `useLayoutEffect`を使った場合、ボックスの高さが変更され、DOMが更新された「直後」に、しかし「まだ画面には表示される前」に`useLayoutEffect`が実行されます。このタイミングで位置調整を行うため、ユーザーは最初から正しい位置に要素が表示されているように見えます。「チラつき」が発生しにくいのが特徴です。

実際にこのコードを動かし、ボタンをクリックしてみてください。`useEffect`側の「位置調整される要素」が、もしかすると一瞬だけ不自然な動きを見せるかもしれません。(環境によっては気づきにくいこともあります。)一方、`useLayoutEffect`側は、常にスムーズな位置調整が行われるはずです。

まとめ:迷ったら`useEffect`、違和感を感じたら`useLayoutEffect`

これで、`useEffect`と`useLayoutEffect`の使い分けが少しクリアになったでしょうか?

最後に、もう一度シンプルな指針をお伝えしますね。

  • ほとんどの場合、`useEffect`で大丈夫です。
  • 外部APIからのデータ取得、イベントリスナーの登録、タイマーの設定、ブラウザのタイトル変更など、DOMの見た目に直接影響を与えない、または影響が一時的で許容範囲内の副作用は、`useEffect`を使いましょう。これがReactの基本的な考え方です。
  • 「あれ?なんか表示が一瞬おかしいな?」「画面がチラつくぞ?」と感じたら、`useLayoutEffect`を検討するサインです。
  • DOMの寸法を測って位置を調整する、アニメーションの初期状態をセットするなど、ユーザーが画面を見る前に、DOMの状態を完璧に整えたい視覚的な操作が必要な場合は、`useLayoutEffect`を使いましょう。

ちょっとだけ注意点

`useLayoutEffect`は、ブラウザの描画前に「同期的に」(つまり、Reactのレンダリング処理と並行して)実行されます。そのため、`useLayoutEffect`の中で非常に重い処理を行うと、画面の描画がブロックされてしまい、ユーザー体験を損なう可能性があります。これは、お客様に開店前の準備をずっと見せて待たせているようなものですね。

なので、`useLayoutEffect`は必要な場合に限定し、その中での処理はできるだけ軽く、素早く終わるものにするのが鉄則です。

大丈夫、焦らず一歩ずつ

Reactの学習は、時に奥深く、時に泥臭い道のりです。でも、一つ一つの概念をこうして丁寧に紐解いていけば、必ず皆さんの血となり肉となります。

`useEffect`と`useLayoutEffect`は、最初は少しややこしく感じるかもしれませんが、現場で「あれ?」と思ったときに「もしかして、useLayoutEffectかな?」とピンとくるようになれば、もうあなたは一歩先のReactエンジニアです!

焦らず、楽しみながら、Reactの深淵を一緒に探求していきましょう!何か困ったことがあれば、いつでも私に相談してくださいね。

コメント

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