設計練習って何をやる時間か
ここまで、コードの書き方そのものはかなり身についてきています。
でも実務に近づくと、いきなり「この画面作って」と言われます。
つまり、「何をどう書くか」の前に、「どう分解して考えるか」が問われ始めます。
この章は、コードを書く前の「設計」を、小さな例で一緒に練習する時間です。
画面構成をざっくり決める
コンポーネントをどう分けるか決める
状態(state)がどこからどこへ流れるかをイメージする
この3つを、ミニアプリを題材にしながら練習していきます。
例題アプリを決める(シンプルな講座一覧ページ)
どんな画面を作るかを言葉で描く
いきなりコードじゃなく、「こんな画面を作りたい」を日本語で決めます。
例題として、「講座一覧ページ」を考えましょう。
画面上部にページタイトルと説明文がある。
その下に「検索ボックス」と「レベル選択(全部 / 初級 / 中級 / 上級)」が並ぶ。
検索条件に応じて、講座カードの一覧が絞り込まれて表示される。
もう少し具体的にすると、こんな感じです。
ページタイトル「講座一覧」
説明文「Next.js × React の講座一覧です。検索やレベルで絞り込めます。」
検索テキスト入力(タイトルに対する部分一致検索)
レベル選択のボタン(全部 / 初級 / 中級 / 上級)
講座カード(タイトル、レベル、簡単な説明)を複数表示
このレベルまで言葉で描ければ、設計の入り口には十分です。
画面構成を考える(「箱」で分けてみる)
大きな「箱」に分けるイメージ
ここでまだコードは書きません。
頭の中か紙の上で、「大きな箱」に画面を分けていきます。
ページ全体の枠(Page)
検索・絞り込みエリア
講座一覧エリア
もっと分けると、
ヘッダー(タイトル+説明)
フィルター(検索ボックス+レベルボタン)
リスト(カードの並び)
1枚のカード
このくらいの粒度がちょうどよいです。
重要なのは、最初から細かく分けすぎないことです。
「明らかに役割が違うまとまり」で区切る、くらいで止めておくと、後で楽になります。
Next.js 的な「ページ」としての位置づけ
例えば、URL は /lessons としましょう。
そうすると、Next.js のファイルは app/lessons/page.tsx になります。
この page.tsx の中に、
ページ全体の構成
フィルタと一覧をどう並べるか
という「画面の骨組み」を書き、その中でコンポーネントを分割していくイメージです。
コンポーネント設計(役割ごとに分ける)
コンポーネント候補を言葉で挙げる
さっきの「箱」を、そのままコンポーネント候補として書き出してみます。
LessonsPage … ページ全体(Next.js の page.tsx)LessonFilter … 検索ボックス+レベルボタンLessonList … フィルタ済みの講座を並べるLessonCard … 1件分の講座カード
ここまでを決めた段階では、まだ「正解・不正解」を考えなくていいです。
大事なのは、「どのコンポーネントが何を担当するか」をざっくり決めることです。
各コンポーネントの責務を一言で説明する
ここから少し実務思考っぽいことをやります。
各コンポーネントについて、「この子の仕事は何か」を一言で説明してみます。
LessonsPage
「全体の状態(講座データ・検索条件)を持ち、Filter と List をつなぐ親」
LessonFilter
「検索テキストとレベルの選択UIを表示し、『検索条件を変えたい』という意思を親に伝える」
LessonList
「渡された講座配列を、カードのリストに変換して表示する」
LessonCard
「講座1件分の情報を見栄えよく表示するだけ。状態は持たない」
こうやって責務をはっきりさせると、
どこが state を持つべきか
どこまでを props で受け取るだけにするか
が見えやすくなります。
状態の流れを描く(state をどこに置くか)
まず「何を状態として持つか」を列挙する
この画面に出てくる「変わりうるもの」は何かを洗い出します。
講座一覧データ(固定のダミーデータでも、将来APIから来るものでも)
検索テキスト
選択中のレベル
この3つは「変わりうる」「UI に影響する」ので、state の候補です。
次に、「誰がそれを使うか」を考えます。
講座一覧データ → LessonList が使う
検索テキスト → LessonFilter が編集し、LessonList が使う
選択中のレベル → LessonFilter が編集し、LessonList が使う
「複数のコンポーネントで使う」ものは、その共通の親に持たせるのが原則でした。
ここでは、共通の親は LessonsPage です。
つまり、state は全部 LessonsPage に置くのが自然です。
状態の流れを矢印でイメージする
言葉だけだとふわっとするので、頭の中で矢印を描いてみます。
LessonsPage に lessons, searchText, selectedLevel の state がある。LessonsPage → LessonFilter に
現在の searchText と selectedLevel を渡す。
さらに「検索条件が変わったら呼んでほしいハンドラ(onChangeSearch / onChangeLevel)」も渡す。
LessonFilter は
input やボタンのイベントで、これらのハンドラを呼ぶだけ。
LessonsPage はsearchText と selectedLevel に基づいてfilteredLessons を計算し、LessonList に渡す。
LessonList は
受け取った lessons を map で LessonCard に変換するだけ。
このように、
状態を持つのは LessonsPageLessonFilter と LessonList は「表示とイベントのきっかけ担当」
という流れにしておくと、状態管理がだいぶスッキリします。
簡易コードで「設計→実装」の橋渡しをする
まずは型と state だけ書いてみる
完全なコードを書く前に、「設計どおりの形になっているか」を軽く確認します。
// app/lessons/page.tsx
"use client";
import { useState } from "react";
type Lesson = {
id: number;
title: string;
level: "初級" | "中級" | "上級";
description: string;
};
const allLessons: Lesson[] = [
{ id: 1, title: "React 入門", level: "初級", description: "コンポーネントの基礎" },
{ id: 2, title: "Next.js 基礎", level: "中級", description: "ページとルーティング" },
{ id: 3, title: "状態管理応用", level: "上級", description: "useReducer と設計" },
];
export default function LessonsPage() {
const [searchText, setSearchText] = useState("");
const [selectedLevel, setSelectedLevel] =
useState<"all" | "初級" | "中級" | "上級">("all");
const filteredLessons = allLessons.filter((lesson) => {
const matchesText = lesson.title.includes(searchText);
const matchesLevel =
selectedLevel === "all" ? true : lesson.level === selectedLevel;
return matchesText && matchesLevel;
});
return (
<main>
{/* ここに LessonFilter と LessonList を置く */}
</main>
);
}
TSXこの段階で、「状態の置き場所」と「絞り込みロジック」をまず固めています。
ここを先に作っておくと、UI 部分はあとからゆっくり肉付けできます。
コンポーネント間の props を設計する
次に、LessonFilter と LessonList の props を型から決めます。
type LessonFilterProps = {
searchText: string;
selectedLevel: "all" | "初級" | "中級" | "上級";
onChangeSearch: (value: string) => void;
onChangeLevel: (value: "all" | "初級" | "中級" | "上級") => void;
};
type LessonListProps = {
lessons: Lesson[];
};
TSXここで大事なのは、「何を受け取って、何を親に返すか」を明確にすることです。
LessonFilter は
表示に必要な今の値(searchText / selectedLevel)
と、「新しい値」を親に渡すための関数(onChangeSearch / onChangeLevel)
LessonList は
「もう絞り込み済み」の配列を受け取るだけ
という構造です。
この時点で、「状態の流れ」がかなり具体的になっています。
実務思考の入口としての設計練習
「いきなり書かない」という贅沢を自分に許す
実務に近づくほど、「すぐ書く人」と「一度立ち止まって考える人」の差が出ます。
すぐ書くと、最初の 30 分は早く進んでいるように見えるけれど、
1〜2 時間後に「やばい、構造が破綻してきた」となりがちです。
反対に、
画面の構成を言葉で整理する
コンポーネントの役割を一言で決める
状態の流れを軽く書き出す
この 10〜15 分を挟むだけで、
後半の迷い方がまったく変わります。
「設計練習」は、あなたにその“贅沢な時間”の取り方をインストールするステップです。
最初は「小さな画面」で訓練する
いきなり大きなダッシュボードの設計をしようとすると、確実に潰れます。
まずは、今回のような小さな画面からでいいです。
講座一覧
TODO一覧+フィルタ
プロフィール編集フォーム
など、「1ページで完結する」ものを題材にして、
画面を箱で分ける
コンポーネントを列挙する
状態の流れを矢印で描く
この 3 ステップを紙でも頭でもいいので毎回やってみる。
それが「実務思考の入口」です。
まとめと、あなたへの宿題
この章でやったことを言い直すと、こうです。
画面構成をコード前に言葉で描くことで、「何を作るのか」を自分に説明できるようにする。
コンポーネントは「役割が違うまとまり」で分け、各コンポーネントの責務を一言で説明してから実装に入る。
状態の流れは、「どの state を誰が使うか」「どこが最も近い共通の親か」を考え、親 → 子への props、子 → 親へのイベントという矢印で整理する。
小さな宿題として、もし余力があれば、
自分で「別の画面」を一つ決めて、
今やった流れ(画面の言語化 → コンポーネント分割 → 状態の流れ)を文章で書いてみてください。
例えば、「お気に入り記事一覧」でも、「簡易チャット画面」でもいいです。
その文章をそのまま見せてもらえれば、
「ここはこう分けるともっと楽になるよ」という、実務寄りのコメントもいくらでも返せます。

