【第13回】扱うデータの形に名前をつける|interface と type(嫁子が教えるTS講座)
今回のゴール
typeでデータの形に名前をつけられるinterfaceとの違いを、迷わない程度に説明できる- 型を先に書いて、実装をAIに渡す進め方が分かる
前回(基本の型注釈)で書き方は出そろいました。今回はそれをまとめて名前をつけます。全体の流れは講座の目次へ。
嫁子: 今日の内容は、第7回の関数と同じ話なの。処理に名前をつけたのが関数で、データの形に名前をつけたのが型。それだけだよ。
しかも今日から、書いた型がそのままAIへの発注書になるの。ここが第3部でいちばん実用的なところだと思う。
① type で名前をつける
前回の長い書き方を、こう置き換えられます。
type Item = {
name: string;
price: number;
done: boolean;
};
const item: Item = { name: "牛乳", price: 250, done: false };
const items: Item[] = [];
一度決めておけば、あとは Item と書くだけです。項目を1つ足したいときも、type の側を直せば、使っている全部の場所に反映されます。
区切りはセミコロン ; でもカンマ , でも動きますが、混ぜないでください。
嫁子: 型の名前は大文字で始めるのが慣習だよ。
ItemとかUserとかね。変数と見分けがつきやすいから、そこは合わせておくといいと思う。AIも大文字で書いてくるから、揃えておくと読みやすいよ。
② 組み合わせる
型は他の型を材料にできます。
type Item = {
name: string;
done: boolean;
};
type ShoppingList = {
title: string;
items: Item[]; // さっき作った型を中で使う
createdAt: string;
};
「リストは、タイトルと、品物の配列と、作成日を持つ」と読めます。日本語の説明とほぼ同じ形で書けます。
前回出てきたユニオン型にも名前をつけられます。
type Status = "todo" | "done" | "canceled";
type Task = {
name: string;
status: Status;
};
Status は「この3つの文字列のどれか」という意味です。それ以外を書くとエラーになります。
const task: Task = { name: "牛乳を買う", status: "finished" };
// エラー: 型 '"finished"' を型 'Status' に割り当てることはできません
嫁子: この書き方、打ち間違いが物理的に不可能になるから好きなんだよね。
"done"を"Done"と書いた瞬間に止まる。第2部でやった「文字列を直接あちこちに書く」やり方だと、こういう間違いは実行するまで分からなかったでしょ。
③ interface との違い
同じことを interface でも書けます。
interface Item {
name: string;
done: boolean;
}
見た目はほぼ同じで、= が無いだけです。違いはいくつかありますが、最初のうちに知っておけばいいのは2つだけです。
type |
interface |
|
|---|---|---|
| オブジェクトの形 | 書ける | 書ける |
ユニオン型("a" \| "b") |
書ける | 書けない |
| 同じ名前で後から項目を足す | できない | できる |
ユニオン型は type でしか書けません。逆に interface は、同じ名前を再度宣言すると項目が合流します(ライブラリの型を拡張するときに使います)。
迷ったら type で構いません。この講座でも type を使います。
嫁子: これ、ネットで検索すると宗教戦争みたいな記事がいっぱい出てくるんだけど、今のあなたには影響が無い議論だよ。
大事なのはプロジェクトの中で揃えることだけ。AIに「型は type で統一して」と最初に言っておけば、勝手に混ざることもないからね。
④ 一部だけ使う・除く
既存の型から新しい型を作る書き方もあります。よく見るのはこの2つです。
type Item = {
id: number;
name: string;
done: boolean;
};
// id を除いた形(新規追加のときは id がまだ無い)
type NewItem = Omit<Item, "id">;
// name だけ取り出した形
type ItemName = Pick<Item, "name">;
Omit は除く、Pick は取り出すです。同じような型を何度も書かずに済みます。
今すぐ使えなくても構いません。AIが書いてきたときに「元の型から作ってるんだな」と読めれば十分です。
⑤ 型を先に書いて、AIに渡す
ここが今回の本題です。前回も少し触れましたが、実際の使い方はこうなります。
まず自分で型だけ書きます。
type Item = {
name: string;
done: boolean;
};
// 品物を追加した新しい配列を返す
function addItem(items: Item[], name: string): Item[] {
}
// 買った・買っていないを反転させた新しい配列を返す
function toggleItem(items: Item[], index: number): Item[] {
}
そのうえで「この型に合わせて中身を書いて」と頼みます。すると、返ってくるものの形は最初から決まっています。
| 決めた人 | 決まること |
|---|---|
| あなた | データの形、関数の入口と出口 |
| AI | 中の実装 |
しかも、AIが型と食い違うものを書いたら、その場でエラーになります。あなたが全部読んで検算しなくても、機械が突き合わせてくれます。
嫁子: 第7回で「仕事を関数の大きさに切って渡す」って言ったでしょ。型はそこに入口と出口の指定を足したものなの。
「買い物リストを作って」と丸投げしたときと比べて、返ってくるものの当たり外れが激減するよ。当たり外れが減ると、確認にかかる時間も減るからね。
つまずきポイント
type の宣言の最後にはセミコロンが要ります。type Item = { ... }; の形です。interface は要りません。忘れても多くの環境では動きますが、揃えておくほうが読みやすくなります。
型はファイルをまたぐと export が必要です。別のファイルから使うときは export type Item = { ... } と書きます。第4部でファイルが増えてから効いてきます。
今回のまとめ
type Item = { ... }でデータの形に名前をつけられる- 型は組み合わせられる。
"todo" | "done"のように取りうる値そのものも型にできる interfaceとはほぼ同じ。迷ったらtype、大事なのは混ぜないことOmit/Pickで既存の型から新しい型を作れる- 型を先に書いて実装をAIに渡すと、返ってくるものの形が最初から決まる
次にやること
次回はジェネリクスの最小限です。Item[] や Promise<Response> のような、中身の型を後から指定する書き方を扱います。名前は難しそうですが、読めるようになるのが目的なので身構えなくて大丈夫です。
今回の宿題:第10回の買い物メモの items に type Item をつけるとどうなるか、実際に書いてみてください。3行で終わります。第16回で、このアプリを丸ごとTypeScriptに書き換えます。
嫁子: 3行書くだけだけど、自分のデータに名前をつけた瞬間から、コードの読み方が変わると思うよ。
itemsが何なのか、もう覚えておかなくていいからね。書いてあるんだから。