【第19回】画面を部品に切って指示しやすくする|コンポーネントとProps(嫁子が教えるTS講座)

今回のゴール

前回(Next.js を動かす)で画面が出るところまで来ました。今回は中身の作り方です。全体の流れは講座の目次へ。


嫁子: 今日の内容は、第7回の関数がそのまま効いてくるよ。

コンポーネントって難しそうな名前だけど、中身は「画面を返す関数」なの。引数を受け取って、結果を返す。第7回でやったことと同じだね。


① コンポーネントは、画面を返す関数

前回いじった app/page.tsx を、こう書き換えてみてください。

export default function Page() {
  return <h1>買い物メモ</h1>;
}

保存すると、ブラウザの表示が変わります。関数が、HTMLのようなものを返しているだけです。

この「HTMLのようなもの」をJSXと呼びます。JavaScriptの中にタグを書ける記法で、変換のときに普通のJavaScriptになります。

関数名は大文字で始めます。小文字だと、Reactは普通のHTMLタグだと判断してしまいます。


② JSXの読み方

3つだけ覚えれば読めます。

中カッコでJavaScriptを埋め込む

const name = "牛乳";
return <li>{name}</li>;   // <li>牛乳</li> になる

返せるのは1つの要素だけ

複数並べたいときは、全体を何かで包みます。意味を持たせたくない場合は空のタグ(フラグメント)を使います。

return (
  <>
    <h1>買い物メモ</h1>
    <p>残り3件</p>
  </>
);

class ではなく className

class はJavaScriptの予約語なので、CSSのクラスを指定するときは className と書きます。

return <span className="done">牛乳</span>;

嫁子: className は本当によく間違えるところだよ。class と書いてもエラーにならず、ただ効かないだけのことがあるからね。

スタイルが当たらないときは、まずここを疑ってみて。


③ Props — 部品に値を渡す

同じ見た目で中身だけ違うものを作りたいときは、値を外から渡します。これがPropsです。関数の引数そのものです。

type ItemRowProps = {
  name: string;
  done: boolean;
};

function ItemRow({ name, done }: ItemRowProps) {
  return <li className={done ? "done" : ""}>{name}</li>;
}

使うときはHTMLの属性のように書きます。

<ItemRow name="牛乳" done={false} />
<ItemRow name="卵" done={true} />

文字列はクォート、それ以外は中カッコで渡します。done="false" と書くと文字列の "false" になってしまい、第4回で見た事故と同じことが起きます。

型を書いてあるので、そこはその場でエラーになります。

嫁子: 第13回で「型が発注書になる」って言ったでしょ。Props の型が、その部品の取扱説明書になるの。

AIに「この部品に価格も表示させて」と頼むと、型のほうも一緒に直してくる。型を見れば、その部品が何を必要としているか分かるから、読む側もラクだね。


④ 配列から一覧を作る

第5回の map がここで効いてきます。

type Item = {
  id: number;
  name: string;
  done: boolean;
};

const items: Item[] = [
  { id: 1, name: "牛乳", done: false },
  { id: 2, name: "卵", done: true },
];

export default function Page() {
  return (
    <ul>
      {items.map((item) => (
        <ItemRow key={item.id} name={item.name} done={item.done} />
      ))}
    </ul>
  );
}

key は必須です。Reactが「どの行がどれか」を見分けるための目印で、書かないと警告が出ます。

key には変わらない一意の値を使ってください。配列の番号(index)でも動きますが、並び替えや削除をすると番号がずれて、表示が入れ替わる事故が起きます。だから id を持たせています。

嫁子: key に index を使ってるコード、AIもたまに書いてくるよ。動くから見逃しがちなの。

削除や並び替えをするリストなら、id を持たせる。これは第10回の買い物メモに無かった発想だから、ここで覚えておいて。


⑤ どこで部品に切るか

切り方に正解はありませんが、目安はあります。

切る基準 例
繰り返し出てくる 一覧の1行、ボタン
名前がつけられる ヘッダー、検索フォーム
独立して直せる 合計金額の表示

「名前がつけられない部品は、切らなくていい」くらいで十分です。

そして切っておくと、AIへの指示がこうなります。

部品に切っていない 切ってある
「一覧の見た目を直して」 「ItemRow の見た目を直して」
周辺まで書き換えられる そのファイルだけ変わる

嫁子: 第7回で「直してほしい場所を名指しできる」って話をしたよね。画面でも同じことをやってるの。

しかも部品ごとにファイルが分かれていれば、AIが触る範囲もそのファイルだけになる。関係ないところを巻き込まれる事故が減るんだよ。


⑥ ファイルを分ける

部品は別ファイルにできます。app/ItemRow.tsx を作って、こう書きます。

type ItemRowProps = {
  name: string;
  done: boolean;
};

export function ItemRow({ name, done }: ItemRowProps) {
  return <li className={done ? "done" : ""}>{name}</li>;
}

使う側で読み込みます。

import { ItemRow } from "./ItemRow";

export と import はセットです。第13回で触れた通り、ファイルをまたぐときに必要になります。


つまずきポイント

コンポーネント名は大文字始まり。itemRow だと動きません。

return のあとに改行するときは丸カッコで包みます。return の直後に何も無いと、JavaScriptが勝手に文を終わらせてしまいます。

return (
  <ul>
    ...
  </ul>
);

JSXの中でコメントを書くときは {/* ... */} です。慣れるまで戸惑いますが、そういう決まりです。


今回のまとめ


次にやること

次回はuseState / useEffectです。今日のコードはまだ固定のデータを表示しているだけなので、ボタンを押すと中身が変わる状態を作ります。第17回で話した「データを変えれば画面が追いつく」が、ここで実物になります。

今回の宿題:ItemRow に price を足して、価格も表示させてみてください。型・渡す側・表示の3か所を直すことになります。1つ足すと3か所が連動するという感覚が、次回以降の土台になります。

嫁子: 3か所と聞くと面倒に思うかもしれないけど、型を直した時点で、残り2か所はエラーが教えてくれるからね。

探す必要はないの。エラーを潰していけば、いつの間にか全部直ってる。これが第3部でやったことの実感だと思う。