この課題のねらい
ここは「React を“なんとなく書ける”から、“設計して書ける”に変わる」ポイントです。
テーマは 状態設計(state をどこに持つか)。
コンポーネントを増やしていくと必ずぶつかるのが、
- どのコンポーネントがどの state を持つべきか
- 親から子に何を渡すべきか
- そもそもこの state、本当に必要か
という問題です。ここを整理できるようになると、一気にコードがスッキリします。
状態設計の基本発想
「誰がその情報を知っているべきか?」で考える
state の持ち場所を考えるときの軸はシンプルで、
「この情報を知っている必要があるコンポーネントはどれか?」
です。
- その情報を使うコンポーネントが 1 つだけなら、そのコンポーネントが持てばいい。
- 複数のコンポーネントで共有したいなら、「共通の親」に state を持たせて、props で渡す。
この「共通の親に state を引き上げる」ことを、よく state のリフトアップ と呼びます。
例題:Todoリストの状態設計
コンポーネント構成をざっくり決める
シンプルな Todo アプリを例にします。
構成イメージはこんな感じです。
TodoApp(全体をまとめる親)TodoInput(新しい Todo を入力するフォーム)TodoList(Todo の一覧表示)TodoItem(1件分の Todo 表示)
ここで考えたいのは、
- Todo の配列(
todos)をどこに持つか - 入力中のテキスト(
inputValue)をどこに持つか
です。
stateの持ち場所を整理する
結論から言うと、こうなります。
todos(Todo の配列)
→TodoAppが持つ(一覧も追加も削除も、全部アプリ全体の話だから)inputValue(入力中のテキスト)
→TodoInputが持つ(フォームの中だけで完結しているから)
図にすると、こんなイメージです(テキストで図を書くと):
TodoApp
├─ state: todos
├─ TodoInput
│ └─ state: inputValue
└─ TodoList
└─ TodoItem(複数)
TodoApp は「アプリ全体の状態(todos)」を管理する親。TodoInput は「フォームの入力状態」を自分で持つ子。TodoList と TodoItem は、todos を props として受け取って表示するだけの“表示専用コンポーネント”にできます。
親子関係を図に書く(もう少し具体的に)
コードと図を対応させてみる
ざっくりしたコード例も見てみましょう。
// TodoApp.tsx
"use client";
import { useState } from "react";
import { TodoInput } from "./TodoInput";
import { TodoList } from "./TodoList";
type Todo = {
id: number;
title: string;
};
export default function TodoApp() {
const [todos, setTodos] = useState<Todo[]>([]);
function handleAddTodo(title: string) {
setTodos((prev) => [
...prev,
{ id: Date.now(), title },
]);
}
return (
<main style={{ padding: "24px" }}>
<h1>Todo アプリ</h1>
<TodoInput onAdd={handleAddTodo} />
<TodoList todos={todos} />
</main>
);
}
TSX// TodoInput.tsx
"use client";
import { useState } from "react";
type TodoInputProps = {
onAdd: (title: string) => void;
};
export function TodoInput({ onAdd }: TodoInputProps) {
const [value, setValue] = useState("");
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!value.trim()) return;
onAdd(value.trim());
setValue("");
}
return (
<form onSubmit={handleSubmit} style={{ marginTop: "12px" }}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Todo を入力"
/>
<button type="submit" style={{ marginLeft: "8px" }}>
追加
</button>
</form>
);
}
TSX// TodoList.tsx
type Todo = {
id: number;
title: string;
};
type TodoListProps = {
todos: Todo[];
};
export function TodoList({ todos }: TodoListProps) {
return (
<ul style={{ marginTop: "16px" }}>
{todos.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
TSXこの構成を図にすると、こうなります。
TodoApp
├─ state: todos
├─ handleAddTodo(title)
├─ TodoInput
│ ├─ state: value
│ └─ props: onAdd(= handleAddTodo)
└─ TodoList
└─ props: todos
ここでのポイントは、
- 「Todo の配列」はアプリ全体で共有したいので、親の
TodoAppが持つ。 - 子の
TodoInputは、「入力中のテキスト」だけを自分で持つ。 TodoListは state を持たず、「渡された todos を表示するだけ」のコンポーネントにする。
この「どのコンポーネントが何を知っているか」を図に書いて整理するのが、状態設計の第一歩です。
挑戦課題:不要なstateを削除する
「それ、本当にstateである必要ある?」を疑う
React を書き慣れてくると、つい何でもかんでも state に入れがちです。
でも、次のようなものは state にしなくていい(=不要な state) です。
- props から計算で導ける値
- 他の state から常に計算できる値
- 一時的な表示用で、レンダリングのたびに計算しても問題ないもの
例を見てみましょう。
const [todos, setTodos] = useState<Todo[]>([]);
const [todoCount, setTodoCount] = useState(0);
useEffect(() => {
setTodoCount(todos.length);
}, [todos]);
TSXこれは「Todo の数」を別 state として持っていますが、
実は todos.length を毎回計算すれば済むので、todoCount は不要です。
不要なstateを削除してスッキリさせる
上の例は、こう書き換えられます。
const [todos, setTodos] = useState<Todo[]>([]);
// どこかの JSX 内
<p>Todo の数: {todos.length}</p>
TSXこれで、
- state が 1 つ減る
- useEffect も不要になる
- 「状態の源泉」が
todosだけになり、バグの余地が減る
というメリットがあります。
状態設計で大事なのは、
「この情報の“本当の元”はどこか?」を一つに決めること
です。
todosが「真の状態」todos.lengthは「そこから計算される派生情報」
なので、state として持つのは todos だけで十分、という判断になります。
状態設計のチェックリスト的な考え方
状態設計をするとき、こんな問いを自分に投げてみてください。
- この state を知る必要があるコンポーネントはどれか?
→ その中で一番上の親に持たせる。 - この値は、他の state や props から計算で出せないか?
→ 出せるなら state にしない。 - 子コンポーネントが「ただ表示するだけ」で済むようにできないか?
→ できるなら、親に state を寄せて、子には props だけ渡す。
このあたりを意識し始めると、
- state の数が減る
- useEffect の数も減る
- 「どこを見れば何が分かるか」が明確になる
ので、コードの見通しが一気によくなります。
まとめ:状態設計でつかんでほしいこと
この課題で本当に持ち帰ってほしいのは、次の感覚です。
- state の持ち場所は「その情報を誰が使うか」で決める。
- 複数のコンポーネントで共有したい state は、共通の親に持たせて props で渡す。
- 図(テキストでもOK)に「親子関係」と「誰がどの state を持つか」を書き出すと、頭が整理される。
- 他の state や props から計算できるものは、state にしない(不要な state を削る)。
