はじめに
Reactは、ユーザーインターフェースを構成するためのJavaScriptライブラリです。近年では、ウェブアプリケーションやモバイルアプリケーションのインターフェースを作成するために、多くの開発現場で使用されています。Reactを学ぶことで、モダンなウェブ開発のスキルを身につけることができるため、フロントエンドエンジニアを目指す上で必須のスキルと言っても過言ではありません。
Reactを学んでいる方の中で、React Hooksについて理解が進まない、どの場面で使うのかよく分からない、と感じる方もいるのではないでしょうか?そんな方に向けてReact Hooksを紹介するシリーズをお届けします。今回は、コンポーネント間で複雑な状態管理をする際に役立つuseReducerについて解説します。この記事を読むことで、**useReducerの基本知識と使い方、useStateとの状態管理の違いを学ぶことができます。**基本を押さえることで、Reactの理解が深まり、ステップアップにつながります。基本を一緒に学び、フロントエンドエンジニアへの第一歩を踏み出しましょう!
この記事について
目的
- useReducerについて知る
- useReducerを使えるようにする
対象者
- 初学者の方
- Reactの学習をしている方
Reactの基本について
Reactの基本の説明はこの記事では省略しています。基本や環境構築方法などを知りたい方は、下記のページをご覧ください。
https://envader.plus/article/341
React Hooksシリーズ
以下はReact Hooksシリーズの記事一覧です。この記事とあわせて読むことで、より理解が深まりますのでぜひご覧ください。
-
Reactの基本、useStateを理解しよう
-
Reactの基本、useEffectを理解しよう
-
Reactの基本、useContextを理解しよう
useReducerとは
useReducerは、Reactコンポーネントの状態(state)を管理するためのReact Hooksの一つです。useStateと同様に、コンポーネント内の状態を管理できますが、useReducerは特に「状態が複雑である場合」や「複数の異なる状態を一つの場所で管理する必要がある場合」に適しています。
useReducerの基本
useReducerの基本的な考え方は、「状態」と「アクション」を使用して状態を更新し、「新しい状態を返す」というものです。ここでの状態は現在のデータの状態を表し、アクションは状態をどのように変化させるかを示します。useReducerの基本として、まず以下の4つの要素を理解しましょう。
-
状態(state)
コンポーネント内で管理するデータや情報です。
-
アクション(action)
状態をどのように更新するかを指定するためのオブジェクトや値です。アクションは通常、typeというプロパティを持ち、どの種類の状態更新を行うかを指定します。
-
dispatch関数(ディスパッチ)
状態を更新するために使用する関数です。アクションを引数として渡します。
-
reducer関数(リデューサー)
この関数は、現在の状態とアクションを受け取り、新しい状態を返します。状態の更新ロジックはこの関数に集約されます。
useReducerでは、状態の更新が一連のルール(リデューサー関数)に基づいて行われます。リデューサー関数は、現在の状態とアクションを受け取り、その結果として新しい状態を返します。たとえば、ユーザーがボタンをクリックしてカウンターの数値を増やしたり減らしたりする処理を行う場合、useReducerを使うと、それらの動作を一つの関数で一元管理できます。
useReducerの処理の流れ
useReducerの基本を学びましたが、まだイメージがつきにくいかもしれません。次に、useReducerで4つの要素がどのような流れで処理されるのか、コード例を通じて更に確認しましょう。
以下のコードは、カウントアップ機能をuseReducerを使用して記述したものです。
// カウントアップ機能 全体のコード
// useReducerをインポート
import { useReducer } from 'react';
// reducer関数の定義
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
// カウントアプリのコンポーネント
function Counter() {
// useReducerフックの呼び出しとstateとdispatchを取得
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<h1>Count : {state.count}</h1>
{/* dispatch関数の定義 */}
<button onClick={() => dispatch({ type: 'increment' })}>
Increment
</button>
<button onClick={() => dispatch({ type: 'decrement' })}>
Decrement
</button>
</div>
);
}
export default Counter;
-
useReducerのインポート
はじめに、コンポーネントのトップレベルでuseReducerをインポートします。
// useReducerをインポート
import { useReducer } from 'react';
-
Reducer関数の定義
reducer関数は状態がどのように変化するかを決める関数です。
function reducer(state, action)
部分で現在の状態と、どのように変えたいかを示すアクションを受け取り、return state;
で変更後の状態を返します。以下のコードでは、
action.type
がincrement
の場合はcount
が1つ増え、decrement
の場合はcount
が1つ減ります。switch
を使用して、どのアクションに対してどのように状態を変えるかを定義します。(条件分岐はif
文でも実装可能です)
// Reducer関数
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
-
useReducerフックの呼び出しと状態の初期値を設定
useReducerフックを呼び出して、状態の初期値を設定します。
// useReducerフックの呼び出し
const [state, dispatch] = useReducer(reducer, { count: 0 });
-
dispatch関数を使用しアクションを送信
状態を更新したいときはdispatch関数を使用し、アクションを渡すことで状態が更新されます。今回の例では、
<button onClick={() => dispatch({ type: 'increment' })}>
に記述されている、dispatch({ type: 'アクション名' })
部分がdispatch関数です。ユーザーの操作(ボタンクリック)をトリガーにして、コンポーネントの現在の状態をどのように更新するかをアクションとしてReducer関数に渡します。
// dispatch関数にアクションを渡す
dispatch({ type: 'increment' }) // 'increment' アクションが渡される
dispatch({ type: 'decrement' }) // 'decrement' アクションが渡される
同じカウントアップ機能をuseStateで実装するとどうなるか
状態の管理や変更は、useStateを使っても実装できます。これまで説明してきたカウントアップ機能をuseStateで記述すると、以下のようになります。
// useStateのインポート
import { useState } from 'react';
// カウントアプリのコンポーネント
function Counter() {
// useStateフックを使用して状態を管理
const [count, setCount] = useState(0);
return (
<div>
<h1>Count : {count}</h1>
{/* カウントを増減させるためのボタン */}
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}
export default Counter;
このように、useReducerを使用するよりもuseStateの方がコードはシンプルになります。今回のような単純なカウントアップ機能であれば、useStateを使う方が、実装が容易になり、コードの可読性も向上し、状態の管理がしやすくなります。
今回はuseReducerをより理解しやすくするために、単純なカウントアップ機能のコードを例に説明しましたが、実装する機能によってはuseStateの方が適している場合があります。
useStateとuseReducerの使い分け
useReducerの基本と、同じカウントアップ機能をuseStateとuseReducerで実装した違いを学びましたが、その使い分けに疑問を持たれた方もいるかと思います。
Reactでは、シンプルな状態管理にはuseStateを、管理が複雑になる場合にはuseReducerを使うのが一般的です。ただし、この2つのHooksの使い分けには明確なルールがあるわけではなく、プロジェクトや具体的な実装の状況に応じて、どちらを使うかを判断するのが良いでしょう。
例えば、1つの入力フォームだけを管理する場合は、useStateで状態を管理するのが簡単で分かりやすいです。しかし、複数の入力フォームがあり、それぞれの入力値を個別に管理するのが大変な場合は、useReducerを使う方が便利です。useReducerを使うことで、すべての入力値を1つのオブジェクトとしてまとめて管理し、状態更新のロジックを一箇所に集約できます。
「すべての入力値を1つのオブジェクトとしてまとめて管理する」とはどういうことでしょうか。この考え方はreduce()
メソッドにもとづいています。
reduce()
というメソッドは、配列を受け取り、多くの値を 1 つの値に「まとめる」ことができるものです。これは「ここまでの結果」と「現在の要素」を受け取り、「次の結果」を返す関数です。 React公式 - なぜリデューサと呼ばれるのか?
React公式ではreduce()
がこのように説明されています。useReducerはこの考え方にもとづいて作られており、ユーザーが1つずつフォームに入力するたびに、複数の状態をまとめて管理し、1つの新しい状態に更新します。例えば、複数のフォームフィールドを持つユーザー登録フォームでは、useReducerを使うことで、各フィールドの状態を一元的に管理でき、コードが整理されやすくなります。
使い分けの具体例
useStateとuseReducerのどちらを使用すべきかを判断する際には、状況に応じた使い分けが重要です。以下に、どのような場面でそれぞれのHooksを使用するか、具体的な例を挙げます。
useStateを使う場合
- 状態がシンプルである(例えば、1つの入力フィールドやトグルスイッチ)
- 状態が他のコンポーネントやアクションに影響されない
useReducerを使う場合
- 状態が複数の関連するフィールドを持っている(例えば、複数の入力フォーム)
- 状態更新のロジックが複雑で、複数のアクションを扱う必要がある
- 状態が複数のコンポーネントで共有されており、統一されたロジックで管理したい
useReducerを使ってみよう
ここまで、useReducerの基本について解説してきました。次に、実際にコードを試して、useReducerの理解を深めていきましょう。このハンズオンでは、以下のTodoアプリを作成します。
ハンズオンを手軽に試したい方には、以下のサイトがおすすめです。Reactの環境構築が不要で、ブラウザ上でコードをすぐに試すことができます。
-
StackBlitz
-
StackBlitzについては、以下のサイトで詳しく解説しています。
useReducerを使用したTodoアプリの作成
このハンズオンで作成するTodoアプリでは、useReducerフックを使ってタスクの追加と削除を管理し、useStateを使って入力されたタスクのテキストを保持します。
以下のファイルを準備し、それぞれ編集します。
- App.jsx
- TodoApp.jsx
まず、プロジェクト内のApp.jsxファイルを以下のように編集します。
// App.jsx
import TodoApp from './TodoApp';
import './App.css';
function App() {
return (
<div>
<h2>ToDo List</h2>
<TodoApp />
</div>
);
}
export default App;
次に、TodoApp.jsxを以下のように編集します。
// TodoApp.jsx
import { useReducer, useState } from 'react';
// Reducer関数:アクションに応じてToDoリストの状態を更新
function todoReducer(todos, action) {
switch (action.type) {
case 'add':
return [...todos, { id: Date.now(), text: action.text }];
case 'delete':
return todos.filter((todo) => todo.id !== action.id);
default:
throw new Error('Unknown action type');
}
}
// TodoAppコンポーネント:ToDoリストのUIとロジックを管理
export default function TodoApp() {
// useReducerでToDoリストの状態を管理 初期値は空のリスト
const [todos, dispatch] = useReducer(todoReducer, []);
// 入力されたテキストの状態を管理
const [text, setText] = useState('');
// タスクを追加する関数:テキストが空でない場合にdispatchを実行
const handleAdd = () => {
if (text.trim()) {
dispatch({ type: 'add', text });
setText('');
}
};
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="タスクを入力してください"
/>
<button onClick={handleAdd}>Add Task</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
{/* タスクを削除するためのボタン:クリックでdispatchを実行 */}
<button onClick={() => dispatch({ type: 'delete', id: todo.id })}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}
コードの編集後、フォームに入力したタスクが追加され、Deleteボタンを押すとタスクが削除されることを確認できます。
コードの解説
ここで、今回作成したコードの確認をしましょう。
Reducer関数
todoReducer
は、ToDoリストの状態(todos)を更新する関数です。アクションの内容に基づいてリストを更新します。
add
アクションでは、新しいタスクをtodos
配列に追加します。新しいタスクには一意のid
と入力されたtext
が含まれます。delete
アクションでは、指定されたid
を持つタスクをtodos
配列から削除します。- それ以外のアクションタイプが渡された場合は、エラーメッセージを返します。
// Reducer関数:アクションに応じてToDoリストの状態を更新
function todoReducer(todos, action) {
switch (action.type) {
case 'add':
return [...todos, { id: Date.now(), text: action.text }];
case 'delete':
return todos.filter((todo) => todo.id !== action.id);
default:
throw new Error('Unknown action type');
}
}
2つのdispatch関数
2つのdispatch関数を使って、Todoの追加と削除のアクションをReducer関数に渡します。
-
dispatch({ type: 'add', text })
タスクを追加するアクションをトリガーします。
todoReducer
のcase 'add'
が実行され、todos
配列に新しいタスクが追加されます。 -
dispatch({ type: 'delete', id: todo.id })
特定のタスクを削除するアクションをトリガーします。
todoReducer
のcase 'delete'
が実行され、指定されたid
のタスクがtodos
配列から削除されます。
// タスクを追加する関数:テキストが空でない場合にdispatchを実行
const handleAdd = () => {
if (text.trim()) {
dispatch({ type: 'add', text });
setText('');
}
{/* タスクを削除するためのボタン:クリックでdispatchを実行 */}
<button onClick={() => dispatch({ type: 'delete', id: todo.id })}>
Delete
</button>
このコードでは、useReducerを使って状態管理を行う方法を学びました。useReducerは、useStateよりも多くの状態やアクションを管理する際に役立つフックです。todoReducer関数が状態をどのように更新するかを決定し、dispatch関数を使ってタスクの追加や削除といったアクションを実行します。
useReducerを使うことで、複数の状態やアクションを効率的に管理できるアプリを作成できるようになります。
この記事で学んだこと
ここまでuseReducerについて、その基本と実際の使い方を学びました。簡単に振り返ってみましょう。
-
useReducerとは
useReducerは、Reactコンポーネントの状態(state)を管理するためのReact Hooksの一つです。useStateと同様に、コンポーネント内の状態を管理できますが、useReducerは特に「状態が複雑である場合」や「複数の異なる状態を一つの場所で管理する必要がある場合」に適しています。
-
useStateとuseReducerの使い分け
Reactでは、シンプルな状態管理にはuseStateを、管理が複雑になる場合にはuseReducerを使うのが一般的です。ただし、この2つのHooksの使い分けには明確なルールがあるわけではなく、プロジェクトや具体的な実装の状況に応じて、どちらを使うかを判断することが重要です。
参考資料
以下のリンクは、この記事で説明した手順や概念に関連する参考資料です。より詳しく学びたい方は、ぜひご参照ください。
-
React公式 - useReducer
-
React公式 - state ロジックをリデューサに抽出する
https://ja.react.dev/learn/extracting-state-logic-into-a-reducer
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2024.09.26
JavaScriptの基礎 async/awaitを理解しよう
非同期処理を扱う方法はJavaScriptにいくつかありますが、今ではasync/awaitが主流となっています。async/awaitは、Promiseよりもシンプルで、コードの見通しが良くなるという利点があります。
- JavaScript
- フロントエンド
2024.04.01
15分で理解!エンジニア初心者向け Vue.js導入ガイド
Vue.jsは、Webアプリケーションのユーザーインターフェース(UI)を構築するためのプログレッシブJavaScriptフレームワークです。簡単に使い始められる一方で、複雑なアプリケーションにも柔軟に対応できるのが特徴です。
- エンジニア
- フロントエンド
- vue
2024.10.17
Reactの基本、React.memoを理解しよう
React.memoは、コンポーネントの再レンダリングを防ぐための仕組みです。
- React
- フロントエンド