React開発者の皆さん、コンポーネントの状態管理に頭を悩ませていませんか?「クラスコンポーネントでの記述が複雑に感じたり、より簡潔でパワフルなコードを書きたい」と考えているなら、この記事はまさにあなたのためにあります。
本記事では、Reactの進化の鍵を握る「React Hooks」について、その基本から具体的な使い方までを徹底解説します。Hooksを学ぶことで、関数コンポーネントだけで状態管理、副作用処理、コンテキストなど、これまでクラスコンポーネントでしかできなかった機能が実現できるようになります。これにより、コードはより読みやすく、再利用性の高いものとなり、開発効率を大幅に向上させることができるでしょう。
さあ、Hooksの扉を開き、モダンなReact開発の最前線へ一緒に踏み出しましょう。
React Hooksとは?関数コンポーネントに革命をもたらす新機能
React Hooksは、Reactバージョン16.8で導入された画期的な機能です。これまでのReactでは、コンポーネント内で状態(state)やライフサイクルメソッド(例: componentDidMountなど)を扱うためには、クラスコンポーネントを使用する必要がありました。しかし、クラスコンポーネントには「thisの扱いの複雑さ」「ロジックの再利用性の低さ」「大規模なコンポーネントの肥大化」といった課題が指摘されていました。
そこで登場したのがHooksです。Hooksを用いることで、関数コンポーネント内で状態やその他のReactの機能を利用できるようになります。これにより、より簡潔で直感的なコード記述が可能になり、開発者はコンポーネントのロジックをより細かく、再利用可能な形で切り出すことができるようになりました。モダンなReact開発において、Hooksは不可欠なツールとなっています。
必須Hooks!useStateとuseEffectでコンポーネントを操る
React Hooksには様々な種類がありますが、その中でも最も頻繁に使用され、基本となるのがuseStateとuseEffectです。これらのHooksを理解することが、React Hooksを使いこなすための第一歩となります。
useStateとは?
useStateは、関数コンポーネントに状態(state)を持たせるためのHookです。クラスコンポーネントのthis.stateとthis.setStateに相当する機能を提供します。シンプルなAPIでコンポーネントの状態を宣言し、更新することができます。
useEffectとは?
useEffectは、関数コンポーネントに副作用(side effect)を定義するためのHookです。データフェッチ、購読の設定、手動でのDOM操作といった、コンポーネントのレンダリングとは直接関係のない処理を管理するのに役立ちます。クラスコンポーネントのcomponentDidMount、componentDidUpdate、componentWillUnmountの各ライフサイクルメソッドの役割を一つで担うことができます。
エンジニアデザイナーさん、ReactのHooksは、関数コンポーネントに「記憶力」や「自動処理」の能力を与えるイメージです。
エンジニア例えば、useStateは、コンポーネントが何かを「覚えておく」ためのフックです。ボタンがクリックされた回数とか、入力フォームに入力された文字とか、変化するデータを管理します。
デザイナーなるほど!Webサイトでいうと、ユーザーが何かアクションを起こした時に、その状態を保持するためのメモ帳みたいなものなんですね!
エンジニアまさにその通りです!そして、useEffectは、そのメモ帳の内容が変わった時や、ページが表示された時に「自動で何かをしてくれるロボット」のようなものです。
デザイナー「自動で何かをする」って、例えばページを読み込んだら自動で最新ニュースを表示したり、フォーム送信後に確認メッセージを出したりするような裏方の動きのことですか?
エンジニア素晴らしい理解ですね!その通りです。コンポーネントの表示とは直接関係ないけれど、コンポーネントの振る舞いを支える重要な処理を担います。
React Hooksの実装例:useStateとuseEffect
それでは、具体的なコードでuseStateとuseEffectの使い方を見ていきましょう。
useStateを使ったカウンターコンポーネント
以下のコードは、ボタンをクリックするとカウントが増えるシンプルなカウンターです。useStateを使ってcountという状態と、それを更新するsetCount関数を定義しています。
import React, { useState } from 'react';
function Counter() {
// countという状態変数と、その状態を更新するためのsetCount関数を定義
const [count, setCount] = useState(0); // 初期値は0
const increment = () => {
setCount(prevCount => prevCount + 1); // setCountを使ってcountを更新
};
return (
<div>
<p>現在のカウント: {count}</p>
<button onClick={increment}>増やす</button>
</div>
);
}
export default Counter;
useEffectを使ったデータフェッチ
次に、useEffectを使ってコンポーネントがマウントされた際に一度だけデータをフェッチする例です。ここでは擬似的なデータフェッチを行っています。
import React, { useState, useEffect } from 'react';
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// コンポーネントがマウントされた後に一度だけ実行
const fetchUserData = async () => {
setLoading(true);
try {
// 擬似的なAPIコール
const response = await new Promise(resolve => setTimeout(() => {
resolve({ name: '太郎', email: 'taro@example.com' });
}, 1000));
setUser(response);
} catch (error) {
console.error("データの取得に失敗しました:", error);
} finally {
setLoading(false);
}
};
fetchUserData();
}, []); // 空の依存配列を渡すことで、マウント時に一度だけ実行
if (loading) {
return <div>ユーザー情報を読み込み中...</div>;
}
if (!user) {
return <div>ユーザー情報が見つかりませんでした。</div>;
}
return (
<div>
<h2>ユーザープロフィール</h2>
<p>名前: {user.name}</p>
<p>メール: {user.email}</p>
</div>
);
}
export default UserProfile;
useEffectの第二引数に空の配列[]を渡すことで、このエフェクトがコンポーネントのマウント時(初回レンダリング後)に一度だけ実行されることを指示しています。これは、クラスコンポーネントのcomponentDidMountに相当します。
基本のフック
useState
useState とは、関数コンポーネントでstateを管理( state の保持と更新)するためのReactフックであり、最も利用されるフックです。 state とはコンポーネントが内部で保持する「状態」のことで、画面上に表示されるデータ等、アプリケーションが保持している状態を指しています。
const App = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>クリック {count} 回</button>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
useEffect
useEffect とは、関数の実行タイミングをReactのレンダリング後まで遅らせるhookです。 副作用の処理(DOMの書き換え、変数代入、API通信などUI構築以外の処理)を関数コンポーネントで扱えます。 クラスコンポーネントでのライフサイクルメソッドに当たります。
const App = () => {
const [count, setCount] = React.useState(0)
const [text, setText] = React.useState('')
const [isShow, setShow] = React.useState(true)
// レンダリングされた時にのみ実行
React.useEffect(() => {
console.log(`レンダリングされました。`)
00}, [])
// count または textが更新された時に実行
React.useEffect(() => {
console.log(`count または textが更新されました。`)
}, [text, count])
// コンポーネントが削除される際に実行
React.useEffect(() => {
return () => {
console.log('コンポーネントが削除されました。')
};
}, [])
return (
<div>
<input type='text' value={text} onChange={e => setText(e.target.value)}></input>
<button onClick={() => setCount(count + 1)}>クリック {count} 回</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
useContext
useContext とは、 Context 機能をよりシンプルに使えるようになった機能。 親からPropsで渡されていないのに、 Context に収容されているデータへよりシンプルにアクセスできるというものです。
// Contextを生成する
const NumberContext = React.createContext();
const App = () => {
return (
<NumberContext.Provider value={42}>
<div>
<Display />
</div>
</NumberContext.Provider>
);
}
const Display = () => {
// Consumerを活用してcontextから取得した値を使う
return (
<NumberContext.Consumer>
{value => <div>親コンポーネントから渡された値: {value}</div>}
</NumberContext.Consumer>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
追加のフック
useReducer
useReducer は useState と同様にstateを管理することができるフックです。 useState との使い分けは、stateが複数の値にまたがるような複雑なロジックがある場合・前回のstateに基づいてstateを更新する場合に useReducer を使うのがよいとされています。
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
}
}
const App = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
useCallback
useCallbackとは パフォーマンス向上のためのフックで、メモ化したコールバック関数を返します。 useEffectと同じように、依存配列の要素のいずれかが変化した場合のみ、メモ化した値を再計算します。
const App = () => {
const [count, setCount] = React.useState(0);
const handleClick = React.useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<button onClick={handleClick}>クリック</button>
<p>count: {count}</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
useMemo
useMemoとは関数の結果を保持するためのフックで、何回やっても結果が同じ場合の値などを保存(メモ化)し、そこから値を再取得します。 不要な再計算をスキップすることから、パフォーマンスの向上が期待出来ます。
const App = () => {
const [countNormal, setCountNormal] = React.useState(0);
const [countHeavy, setCountHeavy] = React.useState(0);
const heavyFunction = (count) => {
let i = 0;
while (i < 1000000000) i++;
return count;
};
// 関数の結果をメモ化する
const heavyFunctionMemo = React.useMemo(() => heavyFunction(countHeavy), [
countHeavy,
]);
return (
<div className="app">
<div className="app-counter">
<div>通常の処理: {countNormal}</div>
<div>重たい処理: {heavyFunction(countHeavy)}</div>
<div>メモ化した処理: {heavyFunctionMemo}</div>
</div>
<div className="app-button">
<button onClick={() => setCountNormal(countNormal + 1)}>通常+1</button>
<button onClick={() => setCountHeavy(countHeavy + 1)}>重たい+1</button>
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
useRef
useRef は元々あった createRef の hooks 版です。 その名の通り、DOMに対する参照を持つために使われるのが主な目的です。 ですがそれ以外の用途にも利用することができます。
const App = () => {
const inputEl = React.useRef(null);
const [text, setText] = React.useState("");
const handleClick = () => {
setText(inputEl.current.value);
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={handleClick}>set text</button>
<p>テキスト : {text}</p>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
useImperativeHandle
useImperativeHandle は Hooks のひとつで、関数コンポーネントにメソッドを追加し、それを親コンポーネントから使えるようにするための機能。
// 公開したいメソッドを持つコンポーネントの定義
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = React.useRef({});
React.useImperativeHandle(ref, () => {
return {
setFocus() {
inputRef.current.focus();
}
};
});
return <input ref={inputRef} type="text" />;
});
const App = () => {
const ref = React.useRef({});
return (
<>
<FancyInput ref={ref} />
<button onClick={() => ref.current.setFocus()}>focus</button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
useLayoutEffect
useEffect と同一ですが、DOM の変更があった後で同期的に副作用が呼び出されます。これは DOM からレイアウトを読み出して同期的に再描画を行う場合に使ってください。
const sleep = (waitMsec) => {
var startMsec = new Date();
while (new Date() - startMsec < waitMsec);
}
const App = () => {
const [text, setText] = React.useState("")
// 画面描画前に実行
React.useLayoutEffect(() => {
sleep(5000);
setText("表示されました!")
});
return <p>{`text:${text}`}</p>
}
ReactDOM.render(<App />, document.getElementById("root"));
useDebugValue
useDebugValue を使って React DevTools でカスタムフックのラベルを表示することができます。
const useRenderCount = () => {
const renderCountRef = React.useRef(0);
React.useDebugValue(
`再描画回数: ${renderCountRef.current}`,
);
React.useEffect(() => {
renderCountRef.current++;
});
};
const App = () => {
useRenderCount();
return <p>DevToolsを確認してください</p>;
};
ReactDOM.render(<App />, document.getElementById("root"));
React Hooksを使いこなして、よりモダンなReact開発へ
React Hooksは、関数コンポーネントの記述に大きな変革をもたらし、より宣言的で再利用性の高いコードを書くことを可能にしました。useStateとuseEffectはHooksの基本中の基本であり、これらを理解し使いこなすことで、Reactアプリケーションの複雑な状態管理や副作用処理を効率的に記述できるようになります。
本記事で学んだことを活かし、ぜひあなたのReactプロジェクトでHooksを積極的に活用してみてください。Hooksの深い理解は、現代のReact開発者にとって不可欠なスキルであり、あなたの開発体験をより豊かなものにしてくれるでしょう。
- React Hooksは関数コンポーネントで状態管理や副作用処理を可能にする。
useStateでコンポーネントの内部状態を、useEffectでデータのフェッチなどの副作用を管理する。- Hooksの導入により、コードの簡潔化、ロジックの再利用性向上、テストのしやすさが実現される。
useStateとuseEffectはReact Hooksの基本であり、モダンなReact開発には不可欠なスキル。