【第19回】画面を部品に切って指示しやすくする|コンポーネントとProps(嫁子が教えるTS講座)
今回のゴール
- コンポーネントがただの関数だと分かる
Propsで値を渡せる。型もつけられる- AIに「この部品だけ直して」と言えるようになる
前回(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の中でコメントを書くときは {/* ... */} です。慣れるまで戸惑いますが、そういう決まりです。
今回のまとめ
- コンポーネントは画面を返す関数。名前は大文字始まり
- JSXは中カッコでJSを埋め込む。返せるのは1要素、
classではなくclassName - Props は関数の引数。型をつけると、その部品の取扱説明書になる
- 一覧は
mapで作り、keyには変わらない一意の値(index は避ける) - 部品に切ると、AIに「この部品だけ直して」と言えるようになる
次にやること
次回はuseState / useEffectです。今日のコードはまだ固定のデータを表示しているだけなので、ボタンを押すと中身が変わる状態を作ります。第17回で話した「データを変えれば画面が追いつく」が、ここで実物になります。
今回の宿題:ItemRow に price を足して、価格も表示させてみてください。型・渡す側・表示の3か所を直すことになります。1つ足すと3か所が連動するという感覚が、次回以降の土台になります。
嫁子: 3か所と聞くと面倒に思うかもしれないけど、型を直した時点で、残り2か所はエラーが教えてくれるからね。
探す必要はないの。エラーを潰していけば、いつの間にか全部直ってる。これが第3部でやったことの実感だと思う。