おう、お疲れさん!今日もReactと向き合ってくれてありがとう。
「useEffect、便利なんだけど、なんかこう、モヤモヤするんだよなぁ…」なんて感じたこと、きっとあるんじゃないか? 大丈夫、それ、キミだけじゃない。現場のベテランだって、たまに「うぐぅ…」って唸ることがあるくらい、useEffectは奥が深いからな。
今日はそんなuseEffectとの付き合い方を、もっとスマートにする秘策を伝授しよう。「カスタムフックへの副作用の抽出」ってやつだ。
「なんだか難しそうな言葉だな…」って思ったかい? 心配いらない。俺がキミの隣に座って、一つずつ丁寧に紐解いていくから、安心してついてきてくれ。
—
☕ 副作用って、そもそも何だ? — お店で買い物をする時に例えてみよう
まず「副作用」っていう言葉の響きが、ちょっと物騒だよね。「副作用」って聞くと、薬の悪い影響みたいなイメージが湧くかもしれない。でも、Reactの世界では、もっと身近で、むしろ必要なことを指すんだ。
お店に買い物に行くシーンを想像してみてくれ。
- メインの目的: 「商品を選んで、レジで会計を済ませる」ことだよね。これがReactでいう「画面にコンポーネントを表示する」っていう、メインの仕事。
- でも、他にも色々なことが起こるよね?:
- お店に入ったら、「いらっしゃいませ!」と店員さんの声が聞こえる。
- 店内には、心地よいBGMが流れている。
- 棚には、商品の説明が書かれたPOPが貼ってある。
- もしかしたら、新商品の試食コーナーがあるかもしれない。
これらって、買い物をする上では「メインじゃない」よね? でも、お店の雰囲気を作ったり、商品を魅力的に見せたり、キミがスムーズに買い物できるように助けてくれる、大事な「お店の演出」だ。
Reactの「副作用」もこれと一緒なんだ。
コンポーネントが画面に表示される(お店が開く)とき、その表示に合わせて「メインじゃないけど、必要なこと」をしたい場合がある。
例えば、
- 画面のタイトルを「商品一覧ページ」に変える(店員さんの声)
- インターネットから最新のデータを取ってくる(POPの情報を更新する)
- タイマーを動かし始める(試食の時間を管理する)
- 画面をスクロールしたら何か反応する(特定の場所で店員さんが声をかける)
これらがReactでいう「副作用」なんだ。そして、この副作用を扱うのが、そう、あの`useEffect`ってフックなんだよ。
😵 useEffectがごちゃごちゃになっちゃう問題
`useEffect`はとっても便利で強力なフックだけど、万能だからこそ、何でもかんでも一つの`useEffect`の中に詰め込みがちになっちゃうことがある。
お店の例で言えば、店長さんが「いらっしゃいませ!」のセリフも、BGMの選曲も、POPの作成も、試食の準備も、全部一人で担当しているようなもの。
しかも、どのタイミングで何をするか、メモがごちゃごちゃになってしまうことも。最初は良くても、お店が大きくなったり、扱う商品が増えたりしたら、もう大変!
Reactのコンポーネントも同じだ。一つの`useEffect`の中に色々な副作用の処理を詰め込みすぎると、
- コードが長くなって読みにくくなる:「あれ、この処理って何のためだっけ?」って迷子になりがちだ。
- 同じような処理を何度も書いちゃう:別のコンポーネントでも似たような副作用が必要になった時、また同じコードをコピペ…なんてことになりやすい。それは間違いのもとにもなるし、もし変更があった時に全部修正する手間が増える。
- 「お片付け」を忘れがちになる:副作用の中には、後で必ず「お片付け(クリーンアップ)」が必要なものもある。例えば、イベントリスナーの解除とか、タイマーの停止とかね。ごちゃごちゃしていると、この大事なお片付けを忘れちゃうことがあるんだ。
まさに、ここがキミがつまずきやすいポイントの一つだと思う。でも大丈夫、この問題を解決するための、とっておきの方法があるんだ。それが「カスタムフック」という設計手法だよ。
🛠️ カスタムフックって何? — 専門の道具や、秘伝のタレをイメージしよう
「カスタムフック」って聞くと、また新しい難しい概念かと身構えるかもしれないけど、全然そんなことない。
お店の例で言えば、店長さんが全ての業務を一人で抱え込むのではなく、
- 「BGM選曲は、このベテラン店員さんにお任せしよう!」
- 「POP作成は、デザインが得意なパートさんに頼もう!」
- 「試食の準備と管理は、食品のプロに任せよう!」
みたいに、特定の作業を専門に引き受けてくれるプロの店員さんや、特定の目的のために作られた専門の道具、あるいは秘伝のタレみたいなものだとイメージしてみてくれ。
Reactの世界では、「特定の副作用の処理」や「特定のロジック」をひとつの関数にまとめて、`use`から始まる名前をつけてあげたものがカスタムフックなんだ。
なぜカスタムフックに抽出するのか? そのメリットは絶大だ!
カスタムフックに抽出することには、たくさんのメリットがある。
1. コンポーネントがスッキリ読みやすくなる:
お店の店長さんが、色々な業務をプロの店員さんに任せたら、店長さんの仕事は「BGM担当さん、よろしく!」「POP担当さん、よろしく!」と指示を出すだけで済むよね。何をお願いしたのか、一目瞭然だ。
Reactのコンポーネントも同じ。複雑な`useEffect`の処理をカスタムフックに切り出せば、コンポーネントの中は「`useBGMPlayer()`」「`usePOPGenerator()`」のように、何をしているのか一目でわかるくらいスッキリするんだ。
2. 同じ処理を使い回せる(再利用性):
一度専門家にお願いする仕組み(カスタムフック)を作ってしまえば、別のお店(別のコンポーネント)でも、同じサービスを簡単に依頼できるよね。
「このコンポーネントでも、あのページでも、スクロール位置を追跡したい!」なんて時、カスタムフックがあれば、同じコードを何度も書く必要がなくなる。コピペ地獄とはもうおさらばだ!
3. 間違いが減って、管理が楽になる(保守性):
専門家にお任せすれば、作業の品質も安定する。例えば、`useEffect`でありがちな「ちゃんとイベントリスナーを解除したっけ?」みたいな「お片付け(クリーンアップ)」の心配事も、カスタムフックの中でしっかり管理できるようになるんだ。
もし処理に変更があった場合も、カスタムフックの中だけを直せばいいから、修正漏れやバグのリスクを減らせる。
どうだ?カスタムフックって、なんだか心強い味方になりそうだろ?
🧑💻 具体例で見てみよう!「画面スクロール追跡のプロ」を雇う
実際に、少し複雑になりがちな`useEffect`の処理を、カスタムフックに切り出す例を見てみよう。
よくあるのが、「今、ページのどのくらいの高さにいるか」を知りたい、というケースだ。例えば、
- ページをある程度スクロールしたら「トップへ戻る」ボタンを表示する。
- ページの最下部に到達したら、次のデータを自動的に読み込む。
なんて機能を作るときに必要になる。
ステップ1: まずはコンポーネントに直接書いてみる(ちょっと長くなっちゃう例)
最初は、カスタムフックを使わずに、普通のコンポーネントの中に直接書いてみよう。
キミが実際にこんなコードを書いて、ちょっと「うーん…」ってなってる姿を想像しながら書くぞ。
import React, { useState, useEffect } from ‘react’;
function MyPageWithDirectEffect() {
const [scrollPosition, setScrollPosition] = useState(0); // 現在のスクロール位置を管理するstate
useEffect(() => {
// ① スクロールイベントが発生したときに実行される関数を定義
const handleScroll = () => {
// window.scrollY で、ページの現在のスクロール位置(一番上からのピクセル数)が取得できます
setScrollPosition(window.scrollY);
};
// ② ページのスクロールイベントが発生したときに、上記 handleScroll 関数を実行するように登録
window.addEventListener(‘scroll’, handleScroll);
console.log(‘— MyPageWithDirectEffect: スクロールイベントリスナーを登録しました!’);
// ③ ★★★超重要!お片付け(クリーンアップ)の関数★★★
// このコンポーネントが画面から消えるとき(お店を閉める時)や、
// 次の新しいスクロール処理が始まる前に、前の処理をきれいにお片付けしないと、
// 無駄な処理が残り続けたり(メモリリーク)、エラーの原因になったりすることがあります。
// useEffectが返す関数は、この「お片付け」のために使われます。
return () => {
window.removeEventListener(‘scroll’, handleScroll); // 登録したイベントリスナーを必ず解除します
console.log(‘— MyPageWithDirectEffect: スクロールイベントリスナーを解除しました!’);
};
}, []); // 依存配列が空なので、このuseEffectはコンポーネントが最初に表示されたときに一度だけ実行されます
// スクロール位置に応じて「トップへ戻る」ボタンを表示するロジック
const showScrollToTopButton = scrollPosition > 200;
// トップへスムーズにスクロールする関数
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: ‘smooth’ });
};
return (
直接useEffectでスクロール位置を追跡するページ
現在のスクロール位置: {scrollPosition}px
{/ スクロール位置が200pxを超えたらボタンを表示 /}
{showScrollToTopButton && (
)}
下にスクロールしてみてください!
スクロールイベントリスナーの登録・解除がコンソールに表示されます。
);
}
export default MyPageWithDirectEffect;
どうだい?「スクロール位置を追跡する」っていう一つの目的のために、結構たくさんのコードが必要になったよね。イベントリスナーの登録と、忘れちゃいけないお片付け。これらがコンポーネントのメインの表示ロジックと混ざり合って、ちょっと読みづらいと感じるかもしれない。
もし、別のコンポーネントでもスクロール位置を追跡したくなったら、このコードをまたコピペ…?それはちょっと手間がかかるし、間違えのもとにもなりかねない。
ステップ2: カスタムフックに切り出してみよう!「スクロール追跡のプロ」の誕生!
さあ、この「スクロール位置を追跡する」という一連の作業を、専門のプロ(カスタムフック)に任せてしまおう。
新しいファイルを作って、そこにこの処理を移すんだ。例えば `useScrollPosition.js` という名前にしてみよう。
// useScrollPosition.js というファイル名で作成します
import { useState, useEffect } from ‘react’;
// カスタムフックの名前は、必ず「use」から始めるのがルールです。
// これがReactに「これはフックだよ!」と教えてあげるサインなんだ。
function useScrollPosition() {
const [scrollPosition, setScrollPosition] = useState(0); // スクロール位置を管理するstateは、フックの内部に閉じ込めます
useEffect(() => {
// スクロールイベントが発生したら、その位置を記録する関数
const handleScroll = () => {
setScrollPosition(window.scrollY);
};
// ページのスクロールイベントが発生したときに、handleScrollを実行するように登録
window.addEventListener(‘scroll’, handleScroll);
console.log(‘— useScrollPosition: スクロールイベントリスナーを登録しました!’);
// ★大事なお片付けも、このカスタムフックの中で責任を持って行います。
// フックを使う側は、このお片付けについて意識する必要がなくなります。
return () => {
window.removeEventListener(‘scroll’, handleScroll); // 登録したイベントリスナーを解除します
console.log(‘— useScrollPosition: スクロールイベントリスナーを解除しました!’);
};
}, []); // 依存配列は空なので、このフックを使ったコンポーネントが最初に表示されたときに一度だけ実行されます
// このフックを使ったコンポーネントに、追跡したスクロール位置を返してあげます
return scrollPosition;
}
export default useScrollPosition;
どうだ?これで「スクロール追跡のプロフェッショナル」である`useScrollPosition`フックが完成したぞ!
このフックを使えば、どのコンポーネントからでも、簡単に現在のスクロール位置を知ることができるようになるんだ。
ステップ3: カスタムフックを使ったコンポーネント(とってもスッキリ!)
さあ、いよいよこのカスタムフックを、キミのコンポーネントで使ってみよう。
先ほどの `MyPageWithDirectEffect` コンポーネントが、どれだけスッキリするか見てくれ!
import React from ‘react’;
import useScrollPosition from ‘./useScrollPosition’; // 作ったカスタムフックをインポートします
function MyPageWithCustomHook() {
// 専門のプロに「スクロール位置、よろしく!」とお願いするだけ!
// useScrollPosition フックが現在のスクロール位置を計算して返してくれます。
const scrollPosition = useScrollPosition();
// スクロール位置に応じて「トップへ戻る」ボタンを表示するロジック
const showScrollToTopButton = scrollPosition > 200;
// トップへスムーズにスクロールする関数
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: ‘smooth’ });
};
return (
カスタムフックでスクロール位置を追跡するページ (スッキリ!)
現在のスクロール位置: {scrollPosition}px
{/ スクロール位置が200pxを超えたらボタンを表示 /}
{showScrollToTopButton && (
)}
下にスクロールしてみてください!
コンソールログで、useScrollPositionの登録・解除を確認できます。
);
}
export default MyPageWithCustomHook;
どうだい?キミのコンポーネントのコードが、とってもスッキリしたと思わないか?
`MyPageWithCustomHook`コンポーネントは、「`useScrollPosition`がスクロール位置を教えてくれるんだな」と、その機能だけを知っていればOK。内部でどうやってスクロールを追跡しているか、イベントリスナーの登録や解除はどうなっているか、といった詳しいことは知る必要がなくなったんだ。
これが、プロの現場でもとっても大事にされている「関心の分離」という考え方だ。それぞれの担当者が、自分の仕事に集中できるようになるってことだな。
💡 もう一歩踏み込む:依存配列の制御もカスタムフックにお任せ
今回の例では、`useEffect`の依存配列を空っぽ(`[]`)にしたよね。これは「コンポーネントが最初に表示された時と、それが消える時にだけ処理をしてね」という意味だった。
でも、カスタムフックの中には、外部から渡された値(例えば、特定のユーザーIDとか、設定値とか)を使って副作用の処理をする場合もある。そんな時は、その値を依存配列に忘れずに含める必要があるんだ。そうすることで、「その値が変わったら、もう一度副作用の処理をやり直してね」とReactに伝えることができる。
この依存配列の管理も、カスタムフックの中に閉じ込めてしまえば、カスタムフックを使う側は「あ、このフックは引数を渡したら勝手にいい感じに処理してくれるんだな」と、あまり気にしなくて良くなる、というメリットもあるんだ。
🚀 まとめ:あなたのReactコードはもっと賢く、もっと美しくなる
Reactの`useEffect`は強力なツールだけど、複雑な処理はカスタムフックに切り出すことで、あなたのコードは格段に読みやすく、再利用しやすくなる。
それはまるで、ごちゃごちゃしていたお店の業務を、専門のプロフェッショナルたちが協力してスマートにこなすようなものだ。キミは店長として、もっとお店全体の運営(コンポーネントの表示)に集中できるようになるってわけだ。
最初は少し難しく感じるかもしれない。でも大丈夫。一歩ずつ、手を動かしながら試していくうちに、きっとあなたのReactスキルはグンと伸びていくから。
- 「あれ、この`useEffect`、ちょっと長すぎないか?」
- 「この副作用の処理、他のコンポーネントでも使い回せそうだな?」
そんな風に感じた時が、カスタムフックの出番を考える絶好のチャンスだ。
「これはひょっとしてカスタムフックにできるんじゃないか?」という視点を持つことが、Reactの設計力を高める第一歩。
さあ、キミも今日から「副作用のプロフェッショナル」を目指して、コードをもっと賢く、もっと美しくしていこうじゃないか!応援してるぞ!

コメント