1. はじめに
「挫折しないReactの教科書」でReactの基礎を学んだので、state管理・イベント処理・レンダリングの仕組み・リスト表示まで、実際に手を動かして理解したことを整理しています。
2. state(状態管理)
ユーザーの操作に応じて変化するデータをReactでどう管理するかを学びました。
2.1 通常の変数ではなくstateが必要な理由
まず通常のJavaScript変数で文字色を変える機能を実装してみました。
通常の変数で文字色を変えようとしたコードです。
import './App.css';
function App() {
// 通常のJavaScript変数を定義
let textColor = 'black';
// ボタンクリック時に色を変える関数
const changeColor = () => {
textColor = 'red';
// コンソールで値の変化を確認
console.log(textColor);
};
return (
<>
{/* 変数の値をcolorに設定 */}
<p style={{ color: textColor }}>この文字の色を変えたい</p>
{/* ボタンクリック時に色を変える関数を呼び出す */}
<button onClick={changeColor}>色を変更</button>
</>
);
}
export default App;
コンソールには red と出力されているのに文字色が変わりません。Reactはコンポーネントが最初に表示されるときに関数を実行してJSXを生成しますが、その後は自動的に画面を更新しないためみたいです。変数の値が変わってもReactがその変更に気づかないため、コンポーネントを再実行して画面を更新できないと理解しました。
2.2 useStateの基本
Reactでstateを使うには useState を使います。useState は配列の分割代入で「現在のstate値」と「stateを更新するための関数」の2つを返してくれると理解しました。
useStateを使ったカウンターのコードです。
import { useState } from 'react';
import './App.css';
function Counter() {
// useStateでstate値と更新関数を取得(初期値は0)
const [count, setCount] = useState(0);
return (
<>
{/* 現在のstate値を表示 */}
<p>現在のカウント:{count}</p>
{/* ボタンクリックでstate値を更新 */}
<button onClick={() => setCount(count + 1)}>カウントアップ</button>
</>
);
}
export default Counter;
ボタンをクリックするたびに setCount(count + 1) が実行されてstateが更新され、Reactが変更を検知して画面を再描画してくれると理解しました。
stateの値を直接変更してしまうとReactが変更を検知できず、画面が更新されません。必ず更新関数を使う必要があります。
// 間違った例:stateを直接変更(動作しない)
count++;
count = count + 1;
// 正しい例:更新関数を使用
setCount(count + 1);
2.3 propsとstateの使い分け
propsとstateはどちらもコンポーネントで扱うデータですが、役割が明確に異なると理解しました。
propsで設定値を受け取り、stateで変化する値を管理するコードです。
import { useState } from 'react';
import './App.css';
// propsで設定値を受け取る
function Counter({ initialValue, maxValue }) {
// propsのinitialValueをstateの初期値として使用
const [count, setCount] = useState(initialValue);
// カウントを増やす関数
const increment = () => {
// countがmaxValueより小さい場合はcountを1増やす
if (count < maxValue) {
setCount(count + 1);
}
};
return (
<>
{/* stateとpropsの両方を表示 */}
<p>カウント:{count} / {maxValue}</p>
<button onClick={increment}>カウントアップ</button>
</>
);
}
function App() {
return (
<>
{/* 異なる設定値でコンポーネントを再利用 */}
<Counter initialValue={10} maxValue={20} />
<Counter initialValue={0} maxValue={5} />
</>
);
}
export default App;
propsは「外から与えられる設定値」としてコンポーネント内で変更できません。一方stateは「コンポーネント内部で変化するデータ」として使い、更新関数を通じて値を変えると理解しました。useState(initialValue) のようにpropsの値をstateの初期値として使い、その後はstateで管理するパターンがよく使われるみたいです。
3. イベント処理
ユーザーの操作を検知して処理を実行する方法を学びました。
3.1 イベント処理の基本
イベントハンドラとはユーザーの操作に対して実行する関数のことみたいです。ReactではJSX内に直接イベントハンドラを指定でき、従来のJavaScriptのように addEventListener で手動登録する必要がないと理解しました。
onClickでボタンクリックを検知するコードです。
import './App.css';
function App() {
// クリック時に実行される関数を定義
const handleClick = () => {
alert('ボタンがクリックされました!');
};
return (
<>
{/* onClickにイベントハンドラを指定 */}
<button onClick={handleClick}>クリックしてください</button>
</>
);
}
export default App;
イベントハンドラには関数自体を渡す必要があります。括弧をつけると即時実行されてしまいます。
// 間違い:括弧()を付けると関数が即座に実行される
<button onClick={increment()}>エラー</button>
// 正しい:括弧なしで関数自体を渡す
<button onClick={increment}>正常</button>
// 引数を渡したい場合はアロー関数で包む
<button onClick={() => setSelected('A')}>正常</button>
3.2 onChangeとonSubmit
テキスト入力とフォーム送信を扱う代表的なイベントも学びました。
onChangeとonSubmitを組み合わせたフォームのコードです。
import { useState } from 'react';
import './App.css';
function App() {
// 入力されたテキストを管理するstate
const [text, setText] = useState('');
// 名前を管理するstate
const [name, setName] = useState('');
// フォーム送信時に実行される関数
const handleSubmit = (e) => {
// ページリロードを防ぐ
e.preventDefault();
alert(`こんにちは、${name}さん!`);
};
return (
<>
<input
type="text"
// stateの値をinput要素の値として設定
value={text}
// 入力が変更された際、stateを更新
onChange={(e) => setText(e.target.value)}
placeholder="テキストを入力"
/>
{/* stateの値を表示 */}
<p>入力:{text}</p>
{/* formのonSubmitにイベントハンドラを設定 */}
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="お名前を入力"
/>
<button type="submit">送信</button>
</form>
</>
);
}
export default App;
onChange のイベントハンドラには e(イベントオブジェクト)が渡されてきます。e.target はinput要素自体を指し、e.target.value でその入力値を取得できると理解しました。e.preventDefault() はブラウザがデフォルトで行うページリロードを防ぐために使うと理解しました。
4. レンダリング
stateを変更すると画面が更新される仕組みを深掘りしました。
4.1 再レンダリングとは
stateの更新をReactが検知して、コンポーネント関数を再実行して画面を再描画するプロセスを「再レンダリング」と呼ぶみたいです。
再レンダリングのタイミングをコンソールで確認するコードです。
import { useState } from 'react';
import './App.css';
function App() {
// レンダリングのたびにコンソールに出力される
console.log('レンダリング');
const [count, setCount] = useState(0);
return (
<>
<p>現在のカウント:{count}</p>
<button onClick={() => setCount(count + 1)}>カウントアップ</button>
</>
);
}
export default App;
ページ表示時に「レンダリング」と出力され(初回レンダリング)、ボタンをクリックするたびにも「レンダリング」と出力されます(再レンダリング)。
開発環境ではStrictModeにより、コンソールに2回ずつ表示されます。これは潜在的な問題を検出するためのものなので、本番環境では1回しか実行されないみたいです。
4.2 再レンダリングが発生するタイミング
再レンダリングは主に2つのタイミングで発生すると理解しました。1つ目はstateが変更されたとき、2つ目は親コンポーネントが再レンダリングされたときです。
親子コンポーネントの再レンダリングを確認するコードです。
import { useState } from 'react';
import './App.css';
// 子コンポーネントを作成
function ChildComponent() {
// 親が再レンダリングされると子も再レンダリングされる
console.log('子コンポーネントがレンダリングされました');
return <p>これは子コンポーネントです</p>;
}
function App() {
console.log('親コンポーネントがレンダリングされました');
const [count, setCount] = useState(0);
return (
<>
<p>現在のカウント:{count}</p>
<button onClick={() => setCount(count + 1)}>カウントアップ</button>
{/* 子コンポーネントを表示 */}
<ChildComponent />
</>
);
}
export default App;
ChildComponent 自身はstateを持っていませんが、親が再レンダリングされたため自動的に再レンダリングされると確認できました。
4.3 useEffectとは
useEffect は「コンポーネントが表示されたとき」「特定のstateが変わったとき」など特定のタイミングで処理を実行できる機能みたいです。
依存配列の違いでuseEffectの実行タイミングを制御するコードです。
import { useState, useEffect } from 'react';
import './App.css';
function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 初回レンダリング時のみ実行(空配列[]を渡す)
useEffect(() => {
console.log('useEffectが実行されました(初回のみ)');
}, []);
// countが変わったときに実行(依存配列に[count]を指定)
useEffect(() => {
console.log(`countが${count}に変わりました`);
}, [count]);
return (
<>
<p>現在のカウント:{count}</p>
<button onClick={() => setCount(count + 1)}>カウントアップ</button>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="テキストを入力"
/>
<p>入力:{text}</p>
</>
);
}
export default App;
依存配列に [count] を指定しているため、text が変わっても useEffect の処理は実行されないと確認できました。
4.4 useEffectの注意点
stateから別のstateを計算するだけの用途には useEffect を使う必要はないみたいです。コンポーネント内の定数として書けば、レンダリングのたびに自動的に再計算されると理解しました。
// 非推奨パターン(useEffectを使ったstateの結合)
useEffect(() => {
setFullName(lastName + ' ' + firstName);
}, [firstName, lastName]);
// 推奨パターン(コンポーネント内に定数として書く)
const fullName = lastName + ' ' + firstName;
4.5 hooksについて
useState や useEffect はhooks(フック)と呼ばれるReactの仕組みの一部で、すべて use で始まる名前になっているみたいです。
hooksの正しい使用場所を示したコードです。
import { useState, useEffect } from 'react';
// 正しい:Reactコンポーネント内でhooksを使用
function MyComponent() {
// コンポーネント内の最上位でのみ呼び出す
const [count, setCount] = useState(0);
useEffect(() => {
console.log('レンダリング完了');
}, []);
return <p>{count}</p>;
}
hooksはコンポーネント内の最上位でのみ呼び出せます。条件分岐やループ内では使用できないみたいです。
5. リストとキー
配列データを一覧として表示する方法を学びました。
5.1 map()を使ったリスト表示
ReactではJavaScriptの map() メソッドを使って配列データをリスト表示できると理解しました。
map()で商品名の配列をリスト表示するコードです。
import './App.css';
function App() {
// 表示する商品名の配列を定義
const productNames = ['ノートPC', 'マウス', 'キーボード', 'タブレット'];
return (
<ul>
{/* map()で配列の各要素をJSX要素に変換 */}
{productNames.map((productName) => (
// 各要素にkey属性を設定(重複しない値を使う)
<li key={productName}>{productName}</li>
))}
</ul>
);
}
export default App;
Reactでリストをレンダリングする際、各要素には必ず重複しない key 属性を設定する必要があるみたいです。key がないとReactがすべての要素を再作成する非効率な更新を行ってしまうと理解しました。
5.2 オブジェクトの配列を表示する
実際のアプリケーションではオブジェクトの配列を扱うことがほとんどみたいです。
オブジェクトの配列をmap()で表示するコードです。
import './App.css';
function App() {
// オブジェクトの配列を定義
const products = [
{ id: 1, name: 'ノートPC', price: 120000, category: 'PC' },
{ id: 2, name: 'マウス', price: 3000, category: '周辺機器' },
{ id: 3, name: 'キーボード', price: 8000, category: '周辺機器' },
{ id: 4, name: 'タブレット', price: 50000, category: 'PC' },
];
return (
<ul>
{/* 各商品のオブジェクトを取り出す */}
{products.map((product) => (
// keyにはidなど一意な値を使うのが一般的
<li key={product.id}>
{/* 複数のプロパティを組み合わせて表示 */}
{product.name}:¥{product.price}
</li>
))}
</ul>
);
}
export default App;
key にはデータベースから取得したIDなど、必ず一意(重複しない)になることが保証されている値を使うのが一般的みたいです。
5.3 filter()との組み合わせ
filter() と map() を組み合わせると、条件に合うデータだけを絞り込んで表示できると理解しました。
filter()で絞り込んでからmap()でリスト表示するコードです。
import './App.css';
function App() {
const products = [
{ id: 1, name: 'ノートPC', price: 120000, category: 'PC' },
{ id: 2, name: 'マウス', price: 3000, category: '周辺機器' },
{ id: 3, name: 'キーボード', price: 8000, category: '周辺機器' },
{ id: 4, name: 'タブレット', price: 50000, category: 'PC' },
];
return (
<ul>
{products
// categoryが'PC'の商品だけを絞り込む
.filter((product) => product.category === 'PC')
// 絞り込んだ結果をJSX要素に変換する
.map((product) => (
<li key={product.id}>
{product.name}:¥{product.price}
</li>
))}
</ul>
);
}
export default App;
filter() は新しい配列を返すため、その結果に対してさらに map() をメソッドチェーンで呼び出せると理解しました。ブラウザで確認すると「PCカテゴリの商品(ノートPCとタブレット)のみ」が表示されました。
まとめ
-
useStateは「現在のstate値」と「更新関数」を配列の分割代入で受け取り、stateの更新は必ずsetCountなどの更新関数を使うと理解しました - propsは「外から与えられる設定値(読み取り専用)」、stateは「コンポーネント内部で変化するデータ」として使い分けると理解しました
- イベントハンドラには
onClick={handleClick}のように括弧なしで関数自体を渡す。引数が必要な場合はonClick={() => setSelected('A')}のようにアロー関数で包むと理解しました -
useEffectの第二引数(依存配列)で実行タイミングを制御でき、空配列[]なら初回のみ、[count]ならcountが変わるたびに実行されると理解しました - hooksはコンポーネント内の最上位でのみ使用でき、条件分岐・ループ内では使えないと理解しました
-
map()で配列データをJSX要素に変換してリスト表示でき、各要素には必ず一意なkey属性を設定すると理解しました -
filter()とmap()を組み合わせることで、条件に合うデータだけを絞り込んで表示できると理解しました