この記事はNuco Advent Calendar 2024の11日目の記事です。
はじめに
Reactは、Web開発において人気の高いライブラリです。しかし、Reactを初めて学ぶとき、多くの人が「ややこしい」「思った以上に難しい」と感じることがあります。その理由の多くは、Reactが単なるツールではなく、「考え方」の学習を必要とするからです。
このガイドでは、React初心者が迷わず進むための11の教えを紹介します。Reactを使いこなすための基礎的な知識と、実践に活かせる考え方を具体例とともに解説します。初心者のつまずきやすいポイントもカバーしていますので、この記事を参考にして、Reactを楽しく学んでください!
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています!興味をお持ちいただける方は、こちらまで。
1. Reactは「考え方」を学ぶための道具と心得るべし
Reactはただのツールではありません。Reactを学ぶ際、単にAPIや構文を覚えるだけではなく、以下のような「考え方」を身につけることが重要です。
1.1 コンポーネントベースの設計
Reactの基本は「コンポーネント」です。コンポーネントは、WebページのUIを構築する小さな単位で、再利用性や管理のしやすさを提供します。
以下の図は、WebページのUIをコンポーネントに分解した例です。
+-------------------------------+
| Header |
+-------------------------------+
| | | |
| Sidebar Main Content |
| | | |
+-------------------------------+
| Footer |
+-------------------------------+
このように、ページ全体を小さなパーツ(Header, Sidebar, Main Content, Footerなど)に分割して設計することがReactの基本です。
1.2 宣言的プログラミング
Reactは「宣言的プログラミング」を採用しています。これは、「何を実現したいか」をコードで宣言し、具体的な手順(命令)はReactに任せる考え方です。
以下は、DOM要素を更新する命令的な方法と、Reactの宣言的な方法の比較です。
命令的プログラミング(Vanilla JavaScript)
const button = document.createElement('button');
button.textContent = 'Click me';
button.onclick = () => alert('Button clicked!');
document.body.appendChild(button);
宣言的プログラミング(React)
function App() {
return (
<button onClick={() => alert('Button clicked!')}>
Click me
</button>
);
}
Reactでは、どのようにボタンがDOMに追加されるかを考える必要がありません。状態に基づいてUIが更新される仕組みをReactが管理してくれます。
1.3 Reactが解決する問題を理解する
Reactの目的は、動的なWebアプリケーションを簡単に構築できるようにすることです。
次のようなケースでReactは特に有効です。
- 頻繁に更新されるUI(例:リアルタイムチャットやダッシュボード)
- 状態に依存する複雑なロジック(例:フォーム入力のバリデーション)
従来の手法では、DOMを直接操作し、手動で更新する必要がありました。しかし、Reactでは以下のように状態とUIが連動します。
このシンプルな仕組みが、Reactを強力なツールにしています。
1.4 まとめ
Reactは、ただのライブラリではなく「考え方」を学ぶための道具です。コンポーネントベースの設計、宣言的プログラミング、Reactが解決する問題を理解することで、Reactの学びをより深いものにしましょう。
2. 宣言的プログラミングの本質を理解するべし
Reactの基本哲学の1つに「宣言的プログラミング」があります。この考え方を理解することは、Reactを使いこなすために非常に重要です。ここでは、宣言的プログラミングの意味と、その実践方法について解説します。
2.1 宣言的プログラミングとは?
宣言的プログラミングは、「何を実現したいか」を記述するアプローチです。
これに対し、命令的プログラミングでは、「どう実現するか」を逐一指示します。
以下のコード例を比較してください。
命令的プログラミング(Vanilla JavaScriptでリストを表示する場合)
const items = ['Apple', 'Banana', 'Cherry'];
const ul = document.createElement('ul');
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
ul.appendChild(li);
});
document.body.appendChild(ul);
宣言的プログラミング(Reactでリストを表示する場合)
function App() {
const items = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{items.map(item => (
<li key={item}>{item}</li>
))}
</ul>
);
}
Reactでは、「リストを表示したい」という目的をコードで宣言するだけで済みます。DOMの操作や要素の生成などの詳細な指示は必要ありません。
2.2 宣言的プログラミングのメリット
Reactが宣言的であることには、以下のような利点があります。
1. 可読性が高い
命令的なコードは、「何をしているのか」だけでなく「どうしているのか」も読み解く必要があります。一方、宣言的なコードは「何をしたいか」にフォーカスしているため、直感的に理解しやすいです。
2. メンテナンスが容易
Reactの宣言的な性質により、UIの状態を管理するコードが明確になります。UIが状態に基づいて自動で更新されるため、メンテナンスが楽です。
3. バグが少ない
ReactがDOMの更新を自動で最適化してくれるため、手動でDOMを操作する際に起こるヒューマンエラーを減らすことができます。
2.3 状態とUIを同期させる
宣言的プログラミングでは、UIは常に状態に基づいてレンダリングされます。この仕組みを活用するためには、状態(State)を正しく管理することが重要です。
以下のコードは、ボタンのクリックによって状態が変わり、それに応じてUIが自動更新される例です。
import React, { useState } from 'react';
function App() {
const [isClicked, setIsClicked] = useState(false);
return (
<button onClick={() => setIsClicked(!isClicked)}>
{isClicked ? 'Clicked!' : 'Click me!'}
</button>
);
}
状態とUIを切り離さない
この例では、isClicked
という状態に応じてボタンのテキストが変化します。状態の変更がUIに自動で反映されるため、開発者は「どう表示を更新するか」を気にする必要がありません。
2.4 Reactと宣言的プログラミングの実践例
Reactでの宣言的プログラミングをより実感するために、小さなプロジェクトを作ってみましょう。
以下は、ToDoリストをReactで宣言的に作成する例です。
import React, { useState } from 'react';
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const addTask = () => {
setTasks([...tasks, newTask]);
setNewTask('');
};
return (
<div>
<h1>ToDo List</h1>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button onClick={addTask}>Add Task</button>
<ul>
{tasks.map((task, index) => (
<li key={index}>{task}</li>
))}
</ul>
</div>
);
}
このコードでは、状態tasks
とnewTask
に基づいてUIが動的に更新されます。追加や削除を命令的に書く必要がなく、状態に集中するだけで済みます。
2.5 まとめ
宣言的プログラミングは、Reactの最大の特長の1つです。「何をしたいか」をコードで記述するだけで、複雑なUIロジックをReactが自動で処理してくれます。この考え方を受け入れることで、React開発がより楽しく、効率的になります。
3. JSXの基礎を押さえ、直感的なコードを書くべし
Reactでは、UIを構築するためにJSX(JavaScript XML)という特殊な構文を使います。JSXはHTMLに似た記述ができるため直感的ですが、初心者が混乱しやすいポイントもいくつかあります。この章では、JSXの基本とその特徴を詳しく解説します。
3.1 JSXとは何か?
JSXは、JavaScript内でHTMLのような構文を書ける仕組みです。これはReactで宣言的プログラミングを実現するための重要な要素です。
JavaScriptでDOMを操作する場合
const element = document.createElement('h1');
element.textContent = 'Hello, world!';
document.body.appendChild(element);
JSXを使ったReactの場合
const element = <h1>Hello, world!</h1>;
Reactでは、JSXを使うことで、よりシンプルで直感的にUIを記述できます。
3.2 JSXのルール
JSXはHTMLに似ていますが、JavaScriptが組み込まれているため、いくつか独自のルールがあります。
1. class
ではなくclassName
を使う
JSXでは、HTMLのclass
属性ではなく、className
を使用します。これは、class
がJavaScriptの予約語であるためです。
CSSクラスを設定する場合
function App() {
return <div className="container">Hello, world!</div>;
}
2. 複数の要素を返すときはラップする
JSXでは、複数の要素を返す場合、単一の親要素でラップする必要があります。
エラーになるコード
function App() {
return (
<h1>Hello, world!</h1>
<p>This is a paragraph.</p>
);
}
正しいコード
function App() {
return (
<div>
<h1>Hello, world!</h1>
<p>This is a paragraph.</p>
</div>
);
}
React.Fragmentを使ったラップ
function App() {
return (
<>
<h1>Hello, world!</h1>
<p>This is a paragraph.</p>
</>
);
}
3.3 JSXでJavaScriptを組み込む方法
JSXの中でJavaScriptを使うには、{}で囲みます。
変数や関数の使用
function App() {
const name = 'React';
return <h1>Welcome to {name}!</h1>;
}
注意: JSX内では、条件分岐やループもJavaScriptとして記述できます。
条件分岐の例:三項演算子を使う
function App() {
const isLoggedIn = true;
return (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}
</div>
);
}
ループの例:map()を使う
function App() {
const items = ['Apple', 'Banana', 'Cherry'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
3.4 JSXの背後にある仕組み
JSXはそのままブラウザで実行されるわけではありません。実際には、BabelというトランスパイラーによってReact.createElement()
に変換されます。
以下のJSXコード
const element = <h1>Hello, world!</h1>;
は、次のように変換されます。
const element = React.createElement('h1', null, 'Hello, world!');
この仕組みを知ることで、JSXの仕組みを深く理解できることでしょう。
3.5 JSXのコツ
1. シンプルに保つ
JSXはUIを記述するためのものであり、複雑なロジックを含めないようにしましょう。複雑なロジックはコンポーネント外の関数に分離するのがおすすめです。
2. 読みやすい構造にする
下の例のようにインデントや改行を適切に使い、コードを整理しましょう。
function App() {
return (
<div className="container">
<h1>Welcome!</h1>
<p>This is a simple React app.</p>
</div>
);
}
3.6 まとめ
JSXはReactでUIを記述するための重要な構文です。HTMLに似た構造で書けるため直感的ですが、いくつかのルールや仕組みを理解することで、より効率的に使いこなせます。JSXをマスターすることで、React開発の第一歩を確実に進めることができます。
4. 小さなコンポーネントに分割して設計するべし
Reactでは、UIを「コンポーネント」という小さな単位に分割して設計します。このアプローチは、コードの再利用性を高め、管理しやすいアプリケーションを構築するうえで非常に重要です。この章では、Reactでのコンポーネント設計の基本を解説します。
4.1 コンポーネントとは?
Reactのコンポーネントは、UIを構成する独立した部品です。それぞれのコンポーネントは、自身の状態やプロパティを持ち、UIの一部を描画します。
シンプルなコンポーネント
function Greeting() {
return <h1>Hello, world!</h1>;
}
このように、Greeting
というコンポーネントは「挨拶」の役割だけを担っています。
4.2 小さなコンポーネントに分割する理由
1. 再利用性が高い
コンポーネントを小さくすることで、他の部分でも再利用しやすくなります。
2. 管理が容易
1つのコンポーネントが小さいと、修正やデバッグが簡単になります。
3. 見通しが良くなる
大規模なアプリケーションでも、コンポーネントごとに責任が分かれていれば、全体を理解しやすくなります。
4.3 コンポーネントの分割方法
1. 親コンポーネントと子コンポーネントに分ける
1つの大きなコンポーネントを複数の小さなコンポーネントに分けます。
分割前
function App() {
return (
<div>
<h1>Welcome</h1>
<p>This is a React application.</p>
<footer>© 2024</footer>
</div>
);
}
分割後
function Header() {
return <h1>Welcome</h1>;
}
function Content() {
return <p>This is a React application.</p>;
}
function Footer() {
return <footer>© 2024</footer>;
}
function App() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
2. 論理的な役割に基づいて分割する
「UIのどの部分が何を担当するか」を考え、それに応じてコンポーネントを分けます。
Todoアプリの場合
-
TodoList
コンポーネント: タスク一覧を表示する -
TodoItem
コンポーネント: 個別のタスクを表示する -
AddTodo
コンポーネント: 新しいタスクを追加する
function TodoItem({ task }) {
return <li>{task}</li>;
}
function TodoList({ tasks }) {
return (
<ul>
{tasks.map((task, index) => (
<TodoItem key={index} task={task} />
))}
</ul>
);
}
function App() {
const tasks = ['Learn React', 'Build an app', 'Have fun'];
return (
<div>
<h1>My Todo List</h1>
<TodoList tasks={tasks} />
</div>
);
}
4.4 コンポーネント設計のコツ
1. 単一責任の原則を守る
1つのコンポーネントは1つの目的(責任)を持つべきです。
悪い例
function Dashboard() {
return (
<div>
<h1>Welcome</h1>
<UserProfile />
<Notifications />
<Settings />
</div>
);
}
このDashboardコンポーネントは、複数の責任を持っています。
良い例
function Dashboard() {
return (
<div>
<Header />
<MainContent />
<Footer />
</div>
);
}
2. 再利用性を意識する
可能な限り、特定の場面だけでなく、他の部分でも使える汎用性の高いコンポーネントを作成しましょう。
3. プレゼンテーションとロジックを分離する
UIを描画する「プレゼンテーションコンポーネント」と、ロジックを担当する「コンテナコンポーネント」を分けると、管理がしやすくなります。
4.5 まとめ
コンポーネントを小さく分割することで、再利用性や可読性が向上し、管理が容易になります。Reactの基本的な強みは、UIを部品化して組み立てる能力にあります。この考え方をしっかりと理解し、実践に取り入れましょう。
5. 「継承」より「合成」を優先するべし
Reactでは、オブジェクト指向プログラミングにおける「継承(Inheritance)」よりも「合成(Composition)」を使うことが推奨されています。合成を行うことで、コードが柔軟で分かりやすくなり、管理が容易になります。この章では、合成する考え方と具体的な使い方を解説します。
5.1 継承とは?合成とは?
継承
継承は、親クラスの機能や状態を子クラスが引き継ぐ仕組みです。オブジェクト指向プログラミングでは一般的なアプローチですが、コードが複雑になりがちです。
合成
合成は、複数のコンポーネントを組み合わせて新しいコンポーネントを作るアプローチです。Reactでは、合成を行うことで、責任が明確で再利用性の高いコードが書けます。
5.2 継承の問題点
Reactでは、以下の理由で継承を避けるべきとされています。
1. 複雑な階層構造を生む
継承を多用すると、親クラスと子クラスの依存関係が複雑になり、保守が困難になります。
2. 柔軟性が低い
継承は1つの親クラスからしか機能を引き継げません(単一継承)。これにより、必要な機能を追加する際に制限が生じます。
3. コードの再利用性が低下
継承を使うと、特定のシナリオに強く依存したコードを書いてしまうことが多くなり、再利用が難しくなります。
5.3 合成の活用例
1. 子コンポーネントを受け渡す
親コンポーネントが子コンポーネントをプロパティとして受け取り、柔軟に構成します。
ダイアログコンポーネント
function Dialog({ title, message, children }) {
return (
<div className="dialog">
<h1>{title}</h1>
<p>{message}</p>
{children}
</div>
);
}
function App() {
return (
<Dialog title="Welcome" message="This is a dialog box.">
<button>Close</button>
</Dialog>
);
}
この例では、Dialog
コンポーネントに子要素(<button>
)を渡すことで、柔軟にUIを構築しています。
2. 特定の責任を持つコンポーネントを組み合わせる
複数のコンポーネントを組み合わせて、より大きなコンポーネントを作ります。
フォームコンポーネント
function Label({ text }) {
return <label>{text}</label>;
}
function Input({ value, onChange }) {
return <input value={value} onChange={onChange} />;
}
function SubmitButton() {
return <button type="submit">Submit</button>;
}
function Form() {
const [value, setValue] = React.useState("");
return (
<form>
<Label text="Enter your name:" />
<Input value={value} onChange={(e) => setValue(e.target.value)} />
<SubmitButton />
</form>
);
}
ここでは、Label
、Input
、SubmitButton
という小さなコンポーネントを組み合わせて、フォーム全体を構築しています。
3. 高階コンポーネントでロジックを共有する
高階コンポーネント(Higher-Order Components, HOC)を使って、コンポーネント間でロジックを共有することもできます。
ロジックの共有
function withUser(Component) {
return function EnhancedComponent(props) {
const user = { name: "Taro Yamada" }; // 仮のユーザーデータ
return <Component {...props} user={user} />;
};
}
function UserProfile({ user }) {
return <p>Welcome, {user.name}!</p>;
}
const EnhancedUserProfile = withUser(UserProfile);
function App() {
return <EnhancedUserProfile />;
}
5.4 合成を選ぶ理由
1. 柔軟性が高い
子コンポーネントを自由に組み合わせることで、UIを簡単に変更できます。
2. テストが容易
小さな独立したコンポーネントをテストすることで、アプリケーション全体の品質を向上させられます。
3. 責任が明確
各コンポーネントが特定の役割を持つため、コードの可読性が向上します。
5.5 まとめ
Reactでは、継承を避け、コンポジションを優先することで、柔軟で管理しやすいコードを書くことができます。コンポーネントを小さく分割し、それらを組み合わせてUIを構築するという考え方を意識して設計を進めましょう。
6. PropsとStateの役割を明確に理解するべし
ReactでUIを構築する際、Props
(プロパティ)とState
(状態)は非常に重要な概念です。それぞれの役割を正しく理解し、適切に使い分けることで、React開発を効率化し、予測可能なコードを書くことができます。この章では、Props
とState
の違いとその使い方を解説します。
6.1 Propsとは?
Props
は「プロパティ」を意味し、親コンポーネントから子コンポーネントへ渡されるデータです。Props
は読み取り専用であり、子コンポーネント内で変更することはできません。
Props
を使ったデータの受け渡し
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
function App() {
return <Greeting name="React" />;
}
この例では、App
コンポーネントがGreeting
コンポーネントにname
というデータを渡しています。Greeting
は、このname
を読み取って表示します。
6.2 Stateとは?
State
は「状態」を意味し、コンポーネント内部で管理される動的なデータです。State
はユーザーの操作やアプリケーションの状態変化に応じて更新されます。
State
を使った動的なUI
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
function App() {
return <Counter />;
}
この例では、useState
フックを使ってcount
という状態を管理しています。setCount
関数を呼び出すことで、count
が更新され、UIが自動的に再描画されます。
6.3 PropsとStateの違い
特性 | Props | State |
---|---|---|
データの所有者 | 親コンポーネント | コンポーネント自身 |
変更可能性 | 読み取り専用(変更不可) | 書き換え可能 |
用途 | 子コンポーネントへのデータ伝達 | コンポーネント内動的データ管理 |
6.4 PropsとStateの組み合わせ
Props
とState
を組み合わせることで、より柔軟なUIを構築できます。
親コンポーネントのState
をProps
として渡す
import React, { useState } from 'react';
function Child({ count, increment }) {
return (
<div>
<p>Count in child: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
function Parent() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
return (
<div>
<h1>Count in parent: {count}</h1>
<Child count={count} increment={increment} />
</div>
);
}
function App() {
return <Parent />;
}
この例では、Parent
コンポーネントがcount
という状態を管理し、それをChild
コンポーネントにProps
として渡しています。Child
での操作がParent
の状態を更新し、それが反映されます。
6.5 PropsとStateのコツ
1. 状態は必要最小限に保つ
状態を持つコンポーネントを増やすと、コードが複雑になります。状態は最小限に保ち、できるだけ親コンポーネントに集約しましょう。
2. Propsはデータ伝達に徹する
Props
はコンポーネント間のデータを渡すためだけに使用します。Props
を変更するような設計は避けましょう。
3. 状態を分割して管理する
複数の状態を1つのオブジェクトで管理すると複雑になりがちです。できるだけシンプルに、必要に応じて状態を分割しましょう。
6.6 よくある間違いとその解決法
間違い 1: Propsを直接変更しようとする
Props
は読み取り専用であり、変更することはできません。
間違ったコード
function Child({ name }) {
name = 'Updated Name'; // エラーになる可能性あり
return <p>{name}</p>;
}
正しいコード
function Parent() {
const [name, setName] = useState('React');
return <Child name={name} />;
}
間違い 2: 状態が不要な場所で使われる
状態が必要ない場合は、単純なProps
の受け渡しで十分です。
6.7 まとめ
Props
とState
はReactでUIを構築するうえで欠かせない要素です。Props
はデータを伝達するために使用し、State
は動的なデータを管理するために使用します。この2つの役割を明確に理解し、適切に組み合わせることで、React開発がより効率的になります。
7. 状態管理は一方向データフローを基盤に構築するべし
Reactの重要な設計思想の1つに「一方向データフロー」があります。これは、アプリケーションの状態が常に親から子へと流れる仕組みを指します。この章では、一方向データフローの基礎を学び、どのように適用するかを解説します。
7.1 一方向データフローとは?
一方向データフローとは、データが常に親コンポーネントから子コンポーネントへ流れる仕組みを指します。Reactではこの仕組みによって、状態が明確で予測可能なものとなり、デバッグや管理が容易になります。
データの流れが一方向であるため、どの部分で状態が変更されるかを追跡しやすくなります。
7.2 一方向データフローの例
以下の例では、Parent
コンポーネントが状態を管理し、そのデータをChild
コンポーネントに渡しています。
親から子へのデータの流れ
import React, { useState } from 'react';
function Child({ count, onIncrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
}
function Parent() {
const [count, setCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1);
};
return (
<div>
<h1>Parent Component</h1>
<Child count={count} onIncrement={handleIncrement} />
</div>
);
}
function App() {
return <Parent />;
}
このコードでは、状態count
はParent
コンポーネントで管理され、Child
コンポーネントにProps
として渡されます。一方向データフローによって、状態の管理場所が明確になります。
7.3 一方向データフローの利点
1. 状態管理がシンプルになる
- 状態がどこで管理され、どこで変更されるかが明確になります。
2. デバッグが容易
- 状態の変更が上位のコンポーネントでのみ発生するため、バグの原因を追跡しやすくなります。
3. 予測可能なUI
- 状態が一貫してUIに反映されるため、予測しやすい動作になります。
7.4 よくある間違いと解決法
間違い 1: 子コンポーネントで状態を直接変更する
子コンポーネントで状態を変更しようとすると、状態管理が複雑になり、一方向データフローの原則が崩れます。
間違ったコード例
function Child({ count }) {
count += 1; // 状態を直接変更しようとする
return <p>Count: {count}</p>;
}
正しいコード例
function Child({ count, onIncrement }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={onIncrement}>Increment</button>
</div>
);
}
間違い 2: 状態を複数の場所で管理する
状態が複数の場所で管理されると、データの同期が難しくなります。状態はできるだけ親コンポーネントに集約しましょう。
7.5 状態管理をシンプルにするためのコツ
1. 状態は「必要な最小限の場所」で管理する
状態は、その状態を必要とするコンポーネントの最も近い共通の親コンポーネントで管理しましょう。
2. Propsを活用する
子コンポーネントへのデータ伝達はProps
を通じて行い、子コンポーネントで状態を持たせすぎないようにします。
3. ロジックは親で、表示は子で分担する
状態管理やイベントハンドリングなどのロジックは親コンポーネントに置き、子コンポーネントはUI表示に専念します。
7.6 状態が複雑な場合の対処法
状態が多くなり、親コンポーネントで管理するのが難しくなった場合は、以下のようなツールや仕組みを活用します。
1. Context API
コンポーネントツリー全体で状態を共有する場合に便利です。
const MyContext = React.createContext();
function App() {
const [value, setValue] = useState('Hello Context');
return (
<MyContext.Provider value={value}>
<Child />
</MyContext.Provider>
);
}
function Child() {
const value = React.useContext(MyContext);
return <p>{value}</p>;
}
2. 状態管理ライブラリ(Redux, Zustand など)
アプリ全体で状態が複雑になった場合に使用します。初期段階ではReactの基本的な状態管理機能で十分です。
7.7 まとめ
一方向データフローはReactの基本的な設計原則であり、状態管理を簡潔で予測可能にします。この原則を守ることで、Reactアプリケーションがスケールしやすくなり、バグも減らすことができます。
8. アプリケーション状態とデータ状態を区別するべし
Reactアプリケーションが複雑になると、「どのデータを状態として管理すべきか」を迷うことがあります。そこで重要なのが、「アプリケーション状態」と「データ状態」を明確に区別することです。この章では、それぞれの違いを理解し、どのように管理すべきかを解説します。
8.1 アプリケーション状態とデータ状態の違い
アプリケーション状態
アプリケーションの動作やUIの状態を管理するデータを指します。これはユーザー操作やアプリケーション内の動きに依存します。
例
• ユーザーがログインしているかどうか(認証状態)。
• ダークモードやライトモードの切り替え。
• モーダルウィンドウが表示されているかどうか。
データ状態
サーバーや外部ソースから取得されたデータ、またはユーザーが入力したデータを指します。これはアプリケーションのロジックやAPIと連携します。
例
• サーバーから取得した商品のリスト。
• ユーザーが入力した検索クエリ。
• フォームの入力値。
8.2 具体例で理解する
以下のToDoリストアプリを例に、それぞれの状態を区別します。
アプリケーション状態
- タスクのフィルター状態: 「すべて表示」「完了済み」「未完了」のどれを表示するか。
- モーダルの開閉状態: 新しいタスクを追加するためのフォームが表示されているか。
データ状態
- タスクのリスト: サーバーから取得したすべてのタスク。
- 新しいタスクの入力値: ユーザーがフォームに入力した値。
8.3 状態の管理方法
1. アプリケーション状態を管理する
アプリケーション状態は、コンポーネント内やReactのContext API、または状態管理ライブラリ(Reduxなど)を使って管理するのが適しています。
アプリケーション状態の管理
import React, { useState } from 'react';
function App() {
const [filter, setFilter] = useState('all'); // アプリケーション状態
return (
<div>
<h1>Todo List</h1>
<button onClick={() => setFilter('all')}>All</button>
<button onClick={() => setFilter('completed')}>Completed</button>
<button onClick={() => setFilter('pending')}>Pending</button>
<p>Current filter: {filter}</p>
</div>
);
}
2. データ状態を管理する
データ状態は、useState
やuseReducer
を使用するほか、サーバーから取得するデータについてはReact QueryやSWRといったデータフェッチライブラリが便利です。
データ状態の管理
import React, { useState } from 'react';
function App() {
const [tasks, setTasks] = useState([]); // データ状態
const [newTask, setNewTask] = useState('');
const addTask = () => {
if (newTask.trim() !== '') {
setTasks([...tasks, { id: tasks.length + 1, text: newTask, completed: false }]);
setNewTask('');
}
};
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button onClick={addTask}>Add Task</button>
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.text}</li>
))}
</ul>
</div>
);
}
8.4 状態の分離がもたらすメリット
1. 管理が容易
アプリケーション状態とデータ状態が分離されていることで、各状態の役割が明確になります。
2. デバッグが簡単
状態が独立しているため、どの状態に問題があるのかが特定しやすくなります。
3. 再利用性が向上
アプリケーション状態とデータ状態を分けることで、コンポーネントの再利用が容易になります。
8.5 コツ
1. 状態を適切な場所で管理する
状態は、その状態を必要とする最も近い共通の親コンポーネントで管理します。これにより、Props
の受け渡しが簡単になります。
2. Context APIを活用する
アプリケーション状態を複数のコンポーネントで共有する場合、ReactのContext APIを使用することで管理がシンプルになります。
3. 外部データはデータフェッチライブラリを活用
React QueryやSWRを使うことで、サーバーからのデータ取得やキャッシュ管理が効率的に行えます。
8.6 よくある間違いとその解決方法
間違い 1: アプリケーション状態とデータ状態を混同する
アプリケーション状態とデータ状態を同じState
で管理すると、状態が増えるにつれて管理が難しくなります。
解決方法: 状態を分離する
- アプリケーション状態(フィルター、モーダル状態)は専用の
State
で管理。 - データ状態(タスクリスト)は別の
State
やデータフェッチライブラリで管理。
間違い 2: 不要な状態を持つ
すべてをStateで管理しようとすると、不要な状態が増えてしまいます。
解決方法: 派生データを計算する
例えば、フィルターされたタスクリストは、元のタスクリストから動的に計算することでState
を削減できます。
8.7 まとめ
アプリケーション状態とデータ状態を明確に区別することで、Reactアプリケーションの設計がシンプルになり、状態管理が容易になります。この区別を意識することで、コードの可読性とメンテナンス性が大幅に向上します。
9. useEffectで副作用を管理するべし
ReactのuseEffect
フックは、副作用を管理するための強力なツールです。副作用(Side Effects)とは、コンポーネントが描画されるたびに発生する、レンダリング以外の処理を指します。この章では、副作用の具体例と、useEffect
を正しく活用する方法を解説します。
9.1 副作用とは?
副作用は、レンダリング以外でアプリケーションに影響を与える処理を指します。
副作用の具体例
- データの取得(APIリクエスト)。
- DOMの操作(スクロール位置の変更など)。
- イベントリスナーの登録や解除。
9.2 useEffectの基本構文
useEffect
は以下のように記述します。
useEffect(() => {
// 副作用の処理
return () => {
// クリーンアップ処理(必要なら)
};
}, [依存配列]);
- 副作用の処理: 主な処理を記述する部分。
- クリーンアップ処理: コンポーネントがアンマウントされるとき、または依存関係が変更されるときに実行されます。
- 依存配列: この配列に指定した値が変更されたときにのみ
useEffect
が再実行されます。
9.3 基本的な例:初回レンダリングでAPIリクエスト
以下は、コンポーネントが初回レンダリングされたときにAPIリクエストを実行する例です。
import React, { useState, useEffect } from 'react';
function App() {
const [data, setData] = useState([]);
useEffect(() => {
// データ取得の副作用
fetch('https://jsonplaceholder.typicode.com/posts')
.then((response) => response.json())
.then((data) => setData(data));
}, []); // 依存配列が空なので、初回レンダリング時のみ実行
return (
<div>
<h1>Posts</h1>
<ul>
{data.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
}
export default App;
- 依存配列を
[]
とすることで、初回レンダリング時にのみ実行されます。 - データを取得し、状態を更新してUIに反映します。
9.4 クリーンアップ処理
副作用によって登録されたリソース(イベントリスナーなど)は、不要になったときに解放(クリーンアップ)する必要があります。これをuseEffect
の戻り値で行います。
イベントリスナーの登録と解除
import React, { useState, useEffect } from 'react';
function App() {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
// ウィンドウサイズを更新する関数
const handleResize = () => {
setWindowWidth(window.innerWidth);
};
// イベントリスナーを登録
window.addEventListener('resize', handleResize);
// クリーンアップ処理:イベントリスナーを解除
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // 初回レンダリング時にのみ登録
return (
<div>
<h1>Window Width: {windowWidth}</h1>
</div>
);
}
export default App;
- イベントリスナーを登録した場合は、不要になったときに解除する必要があります。
- クリーンアップ処理を適切に実装しないと、メモリリークや予期しない動作を引き起こす可能性があります。
9.5 依存配列の重要性
useEffect
の依存配列は、どのタイミングで副作用を再実行するかを制御します。
依存配列のケース
1. 空の配列 []
- 初回レンダリング時のみ実行。
2. 依存値を指定
- 指定した値が変更されるたびに実行。
3. 依存配列を省略
- 毎回レンダリングされるたびに実行(非推奨)。
依存配列を指定する場合
import React, { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count changed: ${count}`);
}, [count]); // countが変更されるたびに実行
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
9.6 よくある間違いとその解決法
間違い 1: 依存配列の指定漏れ
依存配列を正しく指定しないと、useEffect
が期待通りに動作しません。
間違ったコード例
useEffect(() => {
console.log('This will run on every render');
});
修正例
useEffect(() => {
console.log('This will run only when "count" changes');
}, [count]);
間違い 2: クリーンアップ処理の未実装
イベントリスナーやタイマーを解除しないと、メモリリークの原因になります。
修正例
クリーンアップ処理を実装して不要なリソースを解放する。
9.7 コツ
1. 依存配列を明確に定義する
useEffect
内で使用するすべての値を依存配列に指定します。
2. クリーンアップ処理を必ず実装する
特にイベントリスナーやタイマーを使用する場合は必須です。
3. シンプルな処理を心がける
複雑な副作用ロジックは、別の関数やカスタムフックに分離します。
9.8 まとめ
useEffect
を使うことで、副作用を明確に管理し、コンポーネントが予測通りに動作するように制御できます。依存配列やクリーンアップ処理の使い方をマスターすることで、複雑なReactアプリケーションでも安定した状態管理が可能になります。
10. エラーは恐れず学びの友とするべし
React開発において、エラーは避けられないものです。しかし、エラーは学びのチャンスです。エラーを正しく理解し、対処するスキルを身につけることで、React開発者としての成長を加速させることができます。この章では、よくあるエラーの種類とその対処法、エラーから学ぶ心構えを解説します。
10.1 エラーの種類
Reactで発生するエラーは、大きく分けて以下の3つのタイプに分類できます。
1. シンタックスエラー(構文エラー)
JavaScriptやJSXの記述ミスが原因で発生します。コンパイル時に検出され、明確なエラーメッセージが表示されることが多いです。
構文エラー
function App() {
return (
<div>
<h1>Hello, React!<h1> {/* 閉じタグが間違っている */}
</div>
);
}
エラーメッセージ
Error: Adjacent JSX elements must be wrapped in an enclosing tag
対処法
エラーメッセージを確認し、正しい構文に修正します。
function App() {
return (
<div>
<h1>Hello, React!</h1> {/* タグを修正 */}
</div>
);
}
2. ランタイムエラー
コンポーネントがレンダリングされる際に発生するエラーです。たとえば、null
やundefined
の値にアクセスしようとした場合に発生します。
ランタイムエラー
function App() {
const user = null;
return <p>{user.name}</p>; // userがnullなのでエラー
}
エラーメッセージ
TypeError: Cannot read property 'name' of null
対処法
状態やデータが存在することを確認してからレンダリングを行います。
function App() {
const user = null;
return <p>{user ? user.name : 'Guest'}</p>; // デフォルト値を表示
}
3. ロジックエラー
コードは実行されますが、期待した動作をしない場合に発生します。ロジックエラーはエラーメッセージが表示されないため、デバッグが難しいことがあります。
ロジックエラー
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count); // カウントが増加しない
return <button onClick={increment}>Count: {count}</button>;
}
対処法
コードのロジックを確認し、修正します。
const increment = () => setCount(count + 1); // 正しくカウントを増加
10.2 デバッグツールの活用
Reactのエラーを効率的に解決するためには、デバッグツールを活用することが重要です。
1. React Developer Tools
Reactコンポーネントのツリー構造や状態、Props
を可視化できる公式ツールです。
使い方
1. ChromeまたはFirefoxにReact Developer Toolsをインストールします。
2. 開発中のページを開き、「Components」タブでコンポーネントの状態を確認します。
2. ブラウザのコンソール
エラーが発生した場合、詳細なエラーメッセージがブラウザのコンソールに表示されます。これを確認することで、エラー箇所や原因を特定できます。
コンソールのエラー出力
TypeError: Cannot read property 'name' of null
at App (App.js:5)
3. デバッガ
debugger
ステートメントを使用してコードの実行を一時停止し、変数の値を確認します。
デバッガの使用
function App() {
const user = null;
debugger; // 実行がここで一時停止
return <p>{user.name}</p>;
}
10.3 エラーから学ぶ心構え
1. エラーメッセージを読む習慣をつける
Reactのエラーは、原因と解決策を明確に示す場合が多いです。慌てずにエラーメッセージを確認しましょう。
2. エラーは成長のチャンスと捉える
エラーに直面したとき、Reactの仕組みやJavaScriptの基本を深く学ぶ機会と考えましょう。
3. 検索とリファレンスを活用する
エラー内容をGoogleやStack Overflowで検索することで、多くの解決策を見つけることができます。
4. 小さなコード単位で検証する
大きなコードの中でエラーを追うのは難しいため、問題が発生している部分を切り出して単独で確認します。
10.4 よくあるエラーとその解決法
1. コンポーネントのキーが不足している
リストをレンダリングする際、キーを指定しないと警告が表示されます。
{items.map((item) => (
<li>{item.name}</li> // キーが不足
))}
解決法
一意のキーを指定します。
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
2. 状態の更新が遅延する
状態が即時に更新されることを期待している場合、非同期の特性を理解していないと混乱します。
解決法
状態が非同期的に更新されることを認識し、必要に応じてuseEffect
で対処します。
10.5 まとめ
エラーはReactを学ぶ過程で必ず直面するものですが、それを恐れる必要はありません。エラーはReactやJavaScriptの仕組みを深く理解する絶好の機会です。エラーメッセージをよく読み、適切なツールを使って対処することで、React開発スキルを着実に向上させることができます。
11. 小さなプロジェクトから始めるべし
Reactを本格的に学び始めたばかりの初心者にとって、理論やAPIの学習だけでは不十分です。学んだ知識を実践に移すことが重要であり、その第一歩として小さなプロジェクトを作成することを強くおすすめします。この章では、React初心者が取り組みやすいプロジェクトアイデアと、それを通じて得られる学びを解説します。
11.1 なぜ小さなプロジェクトから始めるべきなのか
1. 理論を実践に落とし込む
Reactの基本概念(コンポーネント、状態管理、Props
など)を実際のコードに適用することで、理解が深まります。
2. 成功体験を得る
小規模なプロジェクトは短時間で完成できるため、達成感を得やすく、学習のモチベーションを維持できます。
3. 実践的なエラーに直面する
小さなプロジェクトでもエラーや課題が発生します。それらを解決する過程で、ReactやJavaScriptの理解がより深まります。
11.2 初心者向けのプロジェクトアイデア
以下のプロジェクトは、React初心者が学ぶべき基本概念を網羅しつつ、適度な難易度で取り組むことができます。
1. ToDoリストアプリ
学べること
- 状態管理(
useState
)。 -
Props
を使ったコンポーネント間のデータ受け渡し。 - 配列の操作(追加、削除、更新)。
機能の例
- タスクの追加と削除。
- 完了済みタスクの切り替え。
- フィルタリング(「すべて」「完了」「未完了」)。
コード例(基本形)
import React, { useState } from 'react';
function App() {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const addTask = () => {
if (newTask.trim() !== '') {
setTasks([...tasks, { id: Date.now(), text: newTask, completed: false }]);
setNewTask('');
}
};
const toggleTask = (id) => {
setTasks(
tasks.map((task) =>
task.id === id ? { ...task, completed: !task.completed } : task
)
);
};
return (
<div>
<h1>Todo List</h1>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
/>
<button onClick={addTask}>Add Task</button>
<ul>
{tasks.map((task) => (
<li
key={task.id}
style={{ textDecoration: task.completed ? 'line-through' : 'none' }}
onClick={() => toggleTask(task.id)}
>
{task.text}
</li>
))}
</ul>
</div>
);
}
export default App;
2. カウンターアプリ
学べること
- 状態管理(
useState
)。 - シンプルなイベント処理(クリックで状態を更新)。
機能の例
- カウントの増加・減少。
- リセット機能。
3. メモアプリ
学べること
- 状態管理とフォーム入力。
- ローカルストレージを使ったデータの永続化(オプション)。
機能の例
- テキストの追加、削除、編集。
- テキストの検索機能。
4. 温度変換アプリ
学べること
- 状態管理。
- フォーム入力と双方向データバインディング。
機能の例
- 摂氏(Celsius)と華氏(Fahrenheit)の相互変換。
11.3 小さなプロジェクトで学ぶポイント
1. 状態管理を実践する
プロジェクトの中で、状態を管理し、コンポーネント間でのデータの流れを理解することが重要です。
2. PropsとStateの使い分け
親コンポーネントが状態を管理し、子コンポーネントにProps
として渡す設計を意識しましょう。
3. コンポーネントの再利用を意識する
たとえば、Task
やButton
といったコンポーネントを作成し、複数箇所で再利用する練習をします。
11.4 実践のステップ
1. 最低限の機能から始める
すべての機能を最初から作ろうとせず、最小限の機能でアプリを動かすことを目指します。
2. 機能を1つずつ追加する
1つの機能を完成させてから、次の機能に進みます。これにより、エラーの原因が特定しやすくなります。
3. デバッグツールを活用する
React Developer Toolsやコンソールを使って、状態の変化を確認しながら開発を進めます。
11.5 プロジェクトで得られる成果
- Reactの基本を身につける: 実践を通じてReactの主要な機能を理解できます。
- エラー対応力の向上: 実際にエラーに直面し、その解決方法を学ぶことでスキルが向上します。
- 達成感を得る: 完成したアプリを動かすことで、自信がつき次のステップに進む意欲が湧きます。
11.6 まとめ
小さなプロジェクトを作ることで、Reactの基本を確実に習得し、実践的なスキルを磨くことができます。まずは簡単なアプリを作成し、少しずつ複雑なプロジェクトに挑戦してみましょう。小さな成功体験の積み重ねが、React開発者としての成長を支えます。
おわりに
Reactは、初めて学ぶ際に少しとっつきにくく感じるかもしれません。しかし、Reactの基本的な考え方を理解し、一歩ずつ実践していけば、確実にその魅力と使いやすさを実感できるようになります。
この記事では、React初心者が迷わず進むための11の教えを紹介しました。それぞれの章で解説したポイントを振り返ります。
1. Reactは「考え方」を学ぶための道具と心得るべし
Reactの基盤となる「コンポーネント思考」や「宣言的プログラミング」を理解する。
2. 宣言的プログラミングの本質を理解するべし
「何を実現したいか」をコードで記述し、Reactにロジックの実行を任せる。
3. JSXの基礎を押さえ、直感的なコードを書くべし
HTMLライクなJSX構文を学び、UIを構築する基礎を固める。
4. 小さなコンポーネントに分割して設計するべし
UIを部品化し、責任を明確にした再利用可能な設計を目指す。
5. 「継承」より「合成」を優先するべし
柔軟で簡潔な設計を実現するために、コンポーネントの組み合わせを活用する。
6. PropsとStateの役割を明確に理解するべし
データを渡す役割のProps
と、動的な状態を管理するState
の違いを把握する。
7. 状態管理は一方向データフローを基盤に構築するべし
データが親から子へ流れる仕組みを守り、予測可能なアプリケーションを構築する。
8. アプリケーション状態とデータ状態を区別するべし
状態を役割に応じて分離し、管理をシンプルにする。
9. useEffectで副作用を管理するべし
データ取得やイベントリスナーの管理を適切に行い、安定した動作を実現する。
10. エラーは恐れず学びの友とするべし
React開発中に出会うエラーを理解し、成長の糧にする。
11. 小さなプロジェクトから始めるべし
学んだ知識を実践に移し、Reactのスキルを体系的に習得する。
Reactを学ぶその先へ
Reactの基礎を理解した後は、さらに以下のようなトピックに挑戦することでスキルを広げることができます。
• 状態管理ライブラリ: Redux、Zustand、React Queryなどを使った高度な状態管理。
• ルーティング: React Routerを使ったページ間の遷移。
• パフォーマンス最適化: メモ化(React.memo、useMemo、useCallback)やコード分割。
• テスト: JestやReact Testing Libraryを使ったテストの導入。
• サーバーサイドレンダリング(SSR): Next.jsを使った高速なアプリケーション開発。
最後に
Reactは、初心者から上級者まで幅広い開発者に支持されているライブラリです。その理由は、シンプルで直感的な設計と、柔軟性の高さにあります。最初は学ぶべきことが多いように感じるかもしれませんが、基礎をしっかりと押さえれば、その後の成長がスムーズになります。
この記事が、React学習の道しるべとなり、皆さんの開発スキル向上に役立つことを願っています。Reactを通じて、自由で創造的なWebアプリケーション開発の世界を楽しんでください!
この記事を読み終えたあなたの次のステップは、実際にコードを書いて試してみることです。失敗を恐れず、学び続けることでReactの力を最大限に引き出しましょう!
弊社Nucoでは、他にも様々なお役立ち記事を公開しています。よかったら、Organizationのページも覗いてみてください。
また、Nucoでは一緒に働く仲間も募集しています!興味をお持ちいただける方は、こちらまで。