TypeScript / LESSON 07 / TS07

TypeScript:type と interface

1学習の目的

2基礎解説

TS03で { name: string; price: number }[] のような長い型を書きました。同じ型を何度も書くのは非効率です。名前を付けて再利用しましょう。

やりたいこと書き方備考
型に名前を付けるtype Product = { … }何にでも使える
オブジェクトの形を定義interface Product { … }オブジェクト専用
型を合成するtype A = B & { extra: string }両方の性質を持つ
型を拡張するinterface A extends B { … }interface の書き方
✅ 覚えるべき重要ポイント
  1. type は何にでも名前を付けられる。オブジェクトだけでなく、ユニオン型(TS06)や関数の型にも使える。
  2. interface はオブジェクトの形を定義するためのもの。ユニオン型には使えないが、オブジェクトを表す場合は type とほぼ同じように書ける。
  3. 迷ったら type でよい。使える範囲が広く、書き方も統一できる。interface はライブラリの型定義やクラスとの組み合わせでよく使われる。
  4. & で型を合成できるProduct & { stock: number } は「Productの全項目 + stock」を持つ型になる。
  5. 型は入れ子にできるtype Order = { customer: Customer; items: Product[] } のように、名前を付けた型を部品として組み合わせられる。

現場使用例:APIレスポンスの型定義、共通で使うデータ構造、複数の関数で受け渡すオブジェクト。型に名前が付くと、コードを読む人が構造を一目で把握できる

// type で名前を付ける
type Product = {
  id: number;
  name: string;
  price: number;
};

// 何度でも使い回せる
const item: Product = { id: 1, name: "ノートPC", price: 128000 };
const items: Product[] = [item];

function format(p: Product): string {
  return `${p.name}:${p.price.toLocaleString()}円`;
}

// interface でも同じことができる
interface Customer {
  id: number;
  name: string;
  area: string;
}

// & で型を合成する(両方の項目を持つ)
type StockProduct = Product & { stock: number };

const sp: StockProduct = {
  id: 1, name: "ノートPC", price: 128000, stock: 5,
};

// interface は extends で拡張する
interface VipCustomer extends Customer {
  rank: string;
}

// 型を部品として組み合わせる
type Order = {
  id: number;
  customer: Customer;
  items: Product[];
};

3基本ドリル(10問)

TS07-D01 type で Product 型(id: number, name: string, price: number)を定義し、1件作って「ノートPC:128,000円」の形式で出力せよ。 コーディング★☆☆無料
期待される結果

出力 == ノートPC:128,000円

ヒント

type Product = { id: number; name: string; price: number }; と書く。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
};

const item: Product = { id: 1, name: "ノートPC", price: 128000 };

console.log(`${item.name}:${item.price.toLocaleString()}円`);
解説

型に名前が付くと、以降は Product と書くだけで済む。TS03で毎回書いていた長い型定義から解放される。

TS07-D02 typeinterface の違いとして正しいものはどれか。
A. 完全に同じ B. type はユニオン型にも使えるが interface はオブジェクト専用 C. interface のほうが高速 D. type は非推奨
選択★☆☆無料
期待される結果

解答 == B

ヒント

ユニオン型に名前を付けられるのはどちらか。

模範解答
B
解説

type は使える範囲が広いtype Status = "A" | "B"; は書けるが、interface では書けない。迷ったら type を使えばよい。

TS07-D03 interface で Customer 型(id: number, name: string, area: string)を定義し、1件作って「田中商事(東京)」の形式で出力せよ。 コーディング★☆☆無料
期待される結果

出力 == 田中商事(東京)

ヒント

interface Customer { id: number; name: string; area: string } と書く(イコールは不要)。

模範解答
interface Customer {
  id: number;
  name: string;
  area: string;
}

const c: Customer = { id: 1, name: "田中商事", area: "東京" };

console.log(`${c.name}(${c.area})`);
解説

interface にはイコールが要らないのが type との見た目の違い。中身の書き方はほぼ同じ。

TS07-D04 型に名前を付けるキーワードを補ってコードを完成させよ。 穴埋め★☆☆無料
コード
____ Point = { x: number; y: number };

const p: Point = { x: 3, y: 4 };
console.log(p.x + p.y);
期待される結果

出力 == 7

ヒント

型に別名を付けるキーワード。小文字4文字。

模範解答
type
解説

type は「型に名前を付ける」ためのキーワード。オブジェクト・ユニオン型・関数の型など、どんな型にも使える。

TS07-D05 type A = B & { extra: string } はどういう意味か。
A. B か extra のどちらか B. B の全項目に加えて extra も持つ C. B から extra を除く D. エラーになる
選択★☆☆無料
期待される結果

解答 == B

ヒント

& は「かつ」を表す。

模範解答
B
解説

& は交差型と呼ばれ、両方の性質を併せ持つ型を作る|(どちらか)とは逆の意味になる。

TS07-D06 Product 型(id, name, price)を定義し、それに stock を足した StockProduct 型を & で作れ。1件作って「ノートPC:在庫5個」と出力すること。 コーディング★★☆無料
期待される結果

出力 == ノートPC:在庫5個

ヒント

type StockProduct = Product & { stock: number }; と書く。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
};

type StockProduct = Product & { stock: number };

const item: StockProduct = {
  id: 1,
  name: "ノートPC",
  price: 128000,
  stock: 5,
};

console.log(`${item.name}:在庫${item.stock}個`);
解説

既存の型を土台にして新しい型を作れる。Product が変われば StockProduct も自動的に追随するので、修正漏れが起きない。

TS07-D07 interface を拡張するキーワードを補ってコードを完成させよ。 穴埋め★★☆無料
コード
interface Base { id: number }

interface Item ____ Base {
  name: string;
}

const i: Item = { id: 1, name: "A" };
console.log(i.id, i.name);
期待される結果

出力 == 1 A

ヒント

「拡張する」を意味する7文字のキーワード。

模範解答
extends
解説

interface は extends で拡張する。type の & とほぼ同じ結果になるが、書き方が違う。

TS07-D08 ユニオン型("A" | "B")に名前を付けたいとき、使えるのはどちらか。
A. type のみ B. interface のみ C. どちらも使える D. どちらも使えない
選択★★☆無料
期待される結果

解答 == A

ヒント

interface はオブジェクトの形を定義するもの。

模範解答
A
解説

interface はオブジェクト以外に名前を付けられない。TS06で書いた type Status = … は、type でなければ書けなかった。

TS07-D09 Product 型を定義し、それを引数に取って「ノートPC(128,000円)」を返す関数 label を作れ。2件の商品で試し、2行出力すること(ノートPC/128000、マウス/3200)。 コーディング★★☆無料
期待される結果

出力2行。ノートPC(128,000円) / マウス(3,200円)

ヒント

関数の引数の型に Product を使う。for...of で回す。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
};

function label(p: Product): string {
  return `${p.name}(${p.price.toLocaleString()}円)`;
}

const items: Product[] = [
  { id: 1, name: "ノートPC", price: 128000 },
  { id: 2, name: "マウス", price: 3200 },
];

for (const item of items) {
  console.log(label(item));
}
解説

関数の引数・配列の要素・変数の型、すべてに同じ名前を使える。型名が処理の全体に一貫性を与えている。

TS07-D10 Customer 型(id, name)と Product 型(id, name, price)を定義し、それらを部品として使う Order 型(id: number, customer: Customer, items: Product[])を作れ。1件作って「注文1:田中商事(2点)」と出力すること。 コーディング★★☆無料
期待される結果

出力 == 注文1:田中商事(2点)

ヒント

型の中で他の型を使える。items は Product[] と書く。

模範解答
type Customer = { id: number; name: string };
type Product = { id: number; name: string; price: number };

type Order = {
  id: number;
  customer: Customer;
  items: Product[];
};

const order: Order = {
  id: 1,
  customer: { id: 1, name: "田中商事" },
  items: [
    { id: 1, name: "ノートPC", price: 128000 },
    { id: 2, name: "マウス", price: 3200 },
  ],
};

console.log(`注文${order.id}:${order.customer.name}(${order.items.length}点)`);
解説

型を部品として組み合わせる——TS03で入れ子の型を長々と書いた課題が、こうして整理できる。実務のデータ構造はこの形で表現する。

4実践シナリオ(5問)

TS07-S01 【商品マスタの整理】Product 型(id, name, price, category)を定義し、3件の商品配列からカテゴリが「PC」のものだけを抽出して名前を出力せよ(ノートPC/PC、マウス/周辺機器、モニター/PC)。 コーディング★★☆無料
期待される結果

出力 == ノートPC, モニター

ヒント

型を1回定義すれば、配列にも filter の中でも使える。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
  category: string;
};

const products: Product[] = [
  { id: 1, name: "ノートPC", price: 128000, category: "PC" },
  { id: 2, name: "マウス", price: 3200, category: "周辺機器" },
  { id: 3, name: "モニター", price: 45000, category: "PC" },
];

const pcs = products.filter((p) => p.category === "PC");

console.log(pcs.map((p) => p.name).join(", "));
解説

型定義は1箇所、使用は何箇所でも。もし category を数値に変える必要が出ても、型定義の1行を直せば、使っている全箇所でエラーが出て修正漏れを防げる。

TS07-S02 【型の合成】Product 型(id, name, price)を定義し、& で在庫情報(stock, location)を足した InventoryItem 型を作れ。2件作り、「ノートPC:5個(棚A)」の形式で2行出力すること(ノートPC/5/棚A、マウス/42/棚B)。 コーディング★★☆無料
期待される結果

出力2行が完全一致

ヒント

type InventoryItem = Product & { stock: number; location: string }; と書く。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
};

type InventoryItem = Product & {
  stock: number;
  location: string;
};

const items: InventoryItem[] = [
  { id: 1, name: "ノートPC", price: 128000, stock: 5, location: "棚A" },
  { id: 2, name: "マウス", price: 3200, stock: 42, location: "棚B" },
];

for (const item of items) {
  console.log(`${item.name}:${item.stock}個(${item.location})`);
}
解説

商品情報と在庫情報を分けて定義し、必要な場面で合成している。カタログでは Product、倉庫では InventoryItem と使い分けられる。

TS07-S03 【リテラル型との組み合わせ】Status 型("未発送" | "発送済")と Order 型(id: number, status: Status, amount: number)を定義し、2件の注文から未発送のものの合計金額を「未発送合計:128,000円」の形式で出力せよ(未発送128000、発送済45000)。 コーディング★★☆無料
期待される結果

出力 == 未発送合計:128,000円

ヒント

TS06のリテラル型を type で定義し、Order の中で使う。filter と reduce で集計する。

模範解答
type Status = "未発送" | "発送済";

type Order = {
  id: number;
  status: Status;
  amount: number;
};

const orders: Order[] = [
  { id: 1, status: "未発送", amount: 128000 },
  { id: 2, status: "発送済", amount: 45000 },
];

const total = orders
  .filter((o) => o.status === "未発送")
  .reduce((sum, o) => sum + o.amount, 0);

console.log(`未発送合計:${total.toLocaleString()}円`);
解説

リテラル型を型の部品として使っているo.status === "未発送中" のような打ち間違いは、比較した時点でエラーになる。

TS07-S04 【interface の拡張】Customer 型(id, name, area)を interface で定義し、extends で rank を足した VipCustomer を作れ。通常顧客1件とVIP顧客1件を作り、「田中商事(東京)」「鈴木工業(大阪)★S」の形式で2行出力すること。 コーディング★★★無料
期待される結果

出力2行が完全一致

ヒント

interface VipCustomer extends Customer { rank: string } と書く。VIPだけ ★rank を付ける。

模範解答
interface Customer {
  id: number;
  name: string;
  area: string;
}

interface VipCustomer extends Customer {
  rank: string;
}

const normal: Customer = { id: 1, name: "田中商事", area: "東京" };
const vip: VipCustomer = { id: 2, name: "鈴木工業", area: "大阪", rank: "S" };

console.log(`${normal.name}(${normal.area})`);
console.log(`${vip.name}(${vip.area})★${vip.rank}`);
解説

VipCustomer は Customer としても扱える。Customer を引数に取る関数に VipCustomer を渡せるので、共通処理はまとめて書ける。

TS07-S05 【注文明細の集計】Product 型と OrderLine 型(product: Product, qty: number)を定義し、3件の明細から金額を計算して次の4行を出力せよ。
「ノートPC × 2 = 256,000円」
「マウス × 5 = 16,000円」
「キーボード × 3 = 25,500円」
「合計:297,500円」
コーディング★★★無料
期待される結果

出力4行が完全一致

ヒント

OrderLine の中に Product を入れ子にする。金額は line.product.price * line.qty。

模範解答
type Product = {
  id: number;
  name: string;
  price: number;
};

type OrderLine = {
  product: Product;
  qty: number;
};

const lines: OrderLine[] = [
  { product: { id: 1, name: "ノートPC", price: 128000 }, qty: 2 },
  { product: { id: 2, name: "マウス", price: 3200 }, qty: 5 },
  { product: { id: 3, name: "キーボード", price: 8500 }, qty: 3 },
];

let total = 0;

for (const line of lines) {
  const amount = line.product.price * line.qty;
  total += amount;
  console.log(
    `${line.product.name} × ${line.qty} = ${amount.toLocaleString()}円`
  );
}

console.log(`合計:${total.toLocaleString()}円`);
解説

型の入れ子が、データの入れ子をそのまま表しているline.product.name のように辿れるのは、型が構造を知っているから。打ち間違えれば即座に分かる。

5仕上げ課題

TS07-FINAL 【ECサイトの型設計】ECサイトのデータ構造を型で設計し、注文レポートを出力せよ。

定義する型
Category:「PC」「周辺機器」に限定(リテラル型)
Product{ id: number; name: string; price: number; category: Category }
Customer{ id: number; name: string; area: string }
VipCustomer:Customer に discountRate: number を足した型(& で合成)
OrderLine{ product: Product; qty: number }
Order{ id: number; customer: VipCustomer; lines: OrderLine[] }

データ:VIP顧客「田中商事(東京・割引率0.1)」が、ノートPC(128000/PC)×2 と マウス(3200/周辺機器)×5 を注文

処理:明細を出力し、小計・割引・合計を計算する

期待される出力(5行)
田中商事(東京)様のご注文
[PC] ノートPC × 2 = 256,000円
[周辺機器] マウス × 5 = 16,000円
小計:272,000円
合計:244,800円(割引27,200円)
期待される結果

出力5行が完全一致

ヒント

6つの型を順に定義してから、データを作る。割引額は Math.round(小計 * discountRate)。カテゴリは line.product.category で取り出せる。

模範解答
type Category = "PC" | "周辺機器";

type Product = {
  id: number;
  name: string;
  price: number;
  category: Category;
};

type Customer = {
  id: number;
  name: string;
  area: string;
};

type VipCustomer = Customer & { discountRate: number };

type OrderLine = {
  product: Product;
  qty: number;
};

type Order = {
  id: number;
  customer: VipCustomer;
  lines: OrderLine[];
};

const order: Order = {
  id: 1,
  customer: { id: 1, name: "田中商事", area: "東京", discountRate: 0.1 },
  lines: [
    {
      product: { id: 1, name: "ノートPC", price: 128000, category: "PC" },
      qty: 2,
    },
    {
      product: { id: 2, name: "マウス", price: 3200, category: "周辺機器" },
      qty: 5,
    },
  ],
};

console.log(`${order.customer.name}(${order.customer.area})様のご注文`);

let subtotal = 0;

for (const line of order.lines) {
  const amount = line.product.price * line.qty;
  subtotal += amount;
  console.log(
    `[${line.product.category}] ${line.product.name} × ${line.qty} = ${amount.toLocaleString()}円`
  );
}

const discount = Math.round(subtotal * order.customer.discountRate);
const total = subtotal - discount;

console.log(`小計:${subtotal.toLocaleString()}円`);
console.log(
  `合計:${total.toLocaleString()}円(割引${discount.toLocaleString()}円)`
);
解説

6つの型が組み合わさって、ECサイトのデータ構造を表現している。注目してほしいのは、型定義を読むだけでシステムの全体像が分かることだ。「注文には顧客と明細があり、明細には商品と数量がある」——この関係が、コードを1行も読まずに把握できる。

VipCustomer = Customer & { discountRate: number } という合成も効いている。もし Customer に電話番号を追加したくなったら、1箇所直すだけで VipCustomer にも自動的に反映される。同じ項目を2箇所に書いていたら、必ずどこかで食い違いが生まれていた。

そして Category をリテラル型にしたことで、category: "PC"(全角)のような打ち間違いも防げる。型は小さな部品から組み上げるほど、守備範囲が広がる

ただし今の設計には弱点がある。すべての項目が必須なので、「まだ住所が未登録の顧客」を表現できない。次章のオプショナルと null 安全を学ぶと、この「あるかもしれない項目」を正しく扱えるようになる。