LESSON 12 · 約45分
小さな学習リストを完成し、Next.jsへ進む
学んだ要素を一つの画面へつなぎ、Reactとフレームワークの境界を整理します。
コードを試す前に
01のVite reactテンプレート(JavaScript/JSX)を使います。特記のないApp.jsxは、そのレッスンだけで動く独立した例です。例同士を連結しないでください。
短い学習用の例です。各App.jsxは独立した例で、順に追記して一つの完成品にする形式ではありません。ファイル名、依存関係、未定義部分の前提は注記に記載します。問題内の抜粋や空欄は、そのまま実行するための完成コードではありません。
開発環境の準備を確認する →このレッスンでわかること
- 追加・切り替え・絞り込みを持つ小さなアプリを完成できる
- Reactで学んだこととNext.jsで新たに学ぶことを区別できる
先に完成条件を決める
学習テーマの追加、完了の切り替え、未完了だけの表示、残件数、0件表示を作ります。最初は端末内のstateだけで動かし、再読み込みで戻ることを明示します。ContextやEffectは、学んだからという理由だけでは追加しません。必要な状態は項目・入力・表示条件です。
完成例から、自分の設計へ
下の例でデータの流れを確かめたら、フォームと一覧を別部品へ分けます。項目を管理するstateは共通の親に残し、子には値と通知関数を渡します。idは追加時に一度決め、描画のたびに作り直しません。提出時は、動いた画面だけでなく空欄・全完了・0件・再読み込みも説明します。
Next.jsはReactの先につながる
Next.jsでもコンポーネント、props、stateの知識を使います。App Routerではページやレイアウトは標準でServer Componentです。stateやイベントを使う部品はClient境界へ置きます。Client Componentも初回HTMLの準備にサーバーで関わる場合があり、「use client=すべてブラウザーでしか動かない」とは限りません。次はルート、Server/Client境界、データ取得、保存を順に学びましょう。
- Viteのこの演習ではuse clientは不要
- Next.jsでは対話部品の入口にuse clientを付ける。すべてのファイルに付ける必要はない
- クライアントに置くコードへ秘密のAPIキーを入れない。永続保存や権限は別に設計する
完成例: 今日の学習リスト
import { useRef, useState } from 'react';
export default function App() {
const [items, setItems] = useState([
{ id: 1, title: 'Reactの部品を説明する', done: false },
{ id: 2, title: '四択問題で復習する', done: false },
]);
const [title, setTitle] = useState('');
const [onlyTodo, setOnlyTodo] = useState(false);
const [error, setError] = useState('');
const nextId = useRef(3);
const remaining = items.filter(item => !item.done).length;
const visible = onlyTodo ? items.filter(item => !item.done) : items;
function addItem(event) {
event.preventDefault();
const value = title.trim();
if (!value) { setError('テーマを入力してください。'); return; }
const newItem = { id: nextId.current++, title: value, done: false };
setItems(current => [...current, newItem]);
setTitle('');
setError('');
}
function toggleItem(id) {
setItems(current => current.map(item =>
item.id === id ? { ...item, done: !item.done } : item
));
}
return (
<main>
<h1>今日の学習リスト</h1>
<p>この練習では、再読み込みすると最初の状態に戻ります。</p>
<form onSubmit={addItem} noValidate>
<label htmlFor="new-topic">追加するテーマ</label>
<input id="new-topic" value={title}
onChange={event => { setTitle(event.target.value); setError(''); }}
aria-invalid={Boolean(error)}
aria-describedby={error ? 'add-error' : undefined} />
<button type="submit">追加する</button>
{error && <p id="add-error" role="alert">{error}</p>}
</form>
<label>
<input type="checkbox" checked={onlyTodo}
onChange={event => setOnlyTodo(event.target.checked)} />
未完了だけ表示
</label>
<p role="status">残り{remaining}件 / 全{items.length}件</p>
<ul>
{visible.map(item => (
<li key={item.id}>
<label>
<input type="checkbox" checked={item.done}
onChange={() => toggleItem(item.id)} />
{item.title}({item.done ? '完了' : '未完了'})
</label>
</li>
))}
</ul>
{visible.length === 0 && <p>表示するテーマはありません。</p>}
</main>
);
}このApp.jsxだけで動く統合例です。ID用refの変更はイベント中に行い、state更新関数は純粋に保ちます。データ保存・認証・通信は含みません。
TRY IT YOURSELF
学習リストを仕上げる
- 完成例を動かし、空欄・追加・完了・未完了絞り込み・0件の順に確認する
- AddTopicFormとTopicListへ分割し、stateの持ち主を説明する
- 削除ボタンを追加し、安定したkeyと不変更新を保つ
- 追加→完了→絞り込みを自動テストし、キーボードでも試す
- 再読み込みの挙動と、永続保存を追加するなら何が必要かを説明する
できたらOK:追加・完了・絞り込みが一貫して動き、0件と空欄に対応し、保存範囲とNext.jsで学ぶ次の項目を説明できる
QUICK CHECK
理解を確かめよう
公式資料でもう少し詳しく
ここまで読めたら、ひとつ前進。