【TypeScript 入門教學】#03 interface 與 type:定義物件的形狀

測驗:interface 與 type — 定義物件的形狀

共 5 題,點選答案後會立即顯示結果

1. 在 TypeScript 中,interface 的主要用途是什麼?

  • A. 建立一個新的 JavaScript 類別
  • B. 定義物件的結構,規定物件裡要有哪些欄位和型別
  • C. 宣告一個全域變數供所有檔案使用
  • D. 將物件轉換為 JSON 字串

2. 以下程式碼中,email?: string 的問號 ? 代表什麼意思?

interface User { name: string; age: number; email?: string; }
  • A. email 的值可能是 null
  • B. email 的型別不確定,可以是任何型別
  • C. email 是可選欄位,有沒有都可以
  • D. email 一定要填,但可以是空字串

3. 關於 interfacetype 的差異,以下哪個說法是正確的?

  • A. interface 可以定義聯合型別,type 不行
  • B. type 不能用來定義物件結構
  • C. interface& 來擴展,typeextends
  • D. type 可以定義聯合型別和元組,interface 做不到

4. 以下程式碼會發生什麼事?

interface User { readonly id: number; name: string; } const alice: User = { id: 1, name: “Alice” }; alice.id = 2;
  • A. 正常執行,alice.id 變成 2
  • B. TypeScript 報錯,因為 idreadonly 不能修改
  • C. TypeScript 報錯,因為 id 必須是 string 型別
  • D. 正常執行,但 readonly 會在執行時拋出例外

5. 以下程式碼中,Dog 最終包含哪些欄位?

interface HasName { name: string; } interface HasAge { age: number; } interface Dog extends HasName, HasAge { breed: string; }
  • A. 只有 breed
  • B. namebreed
  • C. nameagebreed
  • D. nameage,但不包含 breed

一句話說明

告訴 TypeScript「這個物件裡面要有哪些欄位、什麼型別」。

前置知識

  • 本系列第 2 篇:基本型別標註(stringnumberboolean 等)
  • JavaScript 的物件操作({ name: "Alice" } 這種寫法)

為什麼需要定義物件的形狀?

AI 幫你寫程式時,經常會產生這樣的程式碼:

interface User {
  name: string;
  age: number;
  email?: string;
}
Code language: PHP (php)

這不是什麼神秘語法。它只是在說:「User 這個物件,必須name(文字)和 age(數字),可以email(文字)。」

你不需要會寫這些定義,但你需要看懂它在規定什麼,因為這會決定整個程式裡物件能用哪些欄位。


interface:定義物件結構

最小範例

interface User {
  name: string;
  age: number;
}

const alice: User = { name: "Alice", age: 25 };
Code language: PHP (php)

逐行翻譯

interface User {        // 定義一個叫 User 的物件結構
  name: string;         // 必須有 name,型別是文字
  age: number;          // 必須有 age,型別是數字
}                       // 結構定義結束

const alice: User = {   // alice 這個變數必須符合 User 的結構
  name: "Alice",        // 有 name,OK
  age: 25,              // 有 age,OK
};
Code language: PHP (php)

翻译:「定義一個 User 結構,裡面一定要有 name(文字)和 age(數字)。alice 這個物件符合 User 結構。」

如果你少寫一個欄位,TypeScript 會報錯:

const bob: User = { name: "Bob" };
// 錯誤!缺少 age 屬性
Code language: JavaScript (javascript)

可選屬性 ?:有也行、沒有也行

最小範例

interface User {
  name: string;
  age: number;
  email?: string;   // 加了 ? 就是可選的
}

const alice: User = { name: "Alice", age: 25 };           // OK,沒有 email 也行
const bob: User = { name: "Bob", age: 30, email: "b@x" }; // OK,有 email 也行
Code language: PHP (php)

翻譯對照

你會看到 意思
name: string 必填,一定要有
email?: string 可選,有沒有都行

重點:問號 ? 放在屬性名稱後面、冒號前面,代表「這個欄位可以不存在」。


唯讀屬性 readonly:設定後不能改

最小範例

interface User {
  readonly id: number;
  name: string;
}

const alice: User = { id: 1, name: "Alice" };
alice.name = "Bob";   // OK,name 可以改
alice.id = 2;         // 錯誤!id 是唯讀的,不能修改
Code language: PHP (php)

翻譯:「readonly 表示這個欄位建立之後就不能再改了。」

常見場景:資料庫的 id、建立時間 createdAt 這類不應該被修改的欄位。


type 別名:幫型別取名字

type 可以幫任何型別取一個名字,不只是物件。

最小範例

type UserID = string | number;

let id1: UserID = "abc";   // OK,是文字
let id2: UserID = 123;     // OK,是數字
let id3: UserID = true;    // 錯誤!布林值不是 string 也不是 number
Code language: JavaScript (javascript)

用 type 定義物件(和 interface 很像)

type User = {
  name: string;
  age: number;
  email?: string;
};

const alice: User = { name: "Alice", age: 25 };  // 用法跟 interface 一模一樣
Code language: JavaScript (javascript)

翻譯對照

你會看到 意思
type UserID = string 「UserID 就是 string 的別名」
type UserID = string \| number 「UserID 可以是 string 或 number」
type User = { name: string } 「User 是一個有 name 欄位的物件」

interface vs type:差在哪?

這是很多人困惑的地方,但對 Vibe Coder 來說,記住兩件事就好:

差異一:擴展方式不同

// interface 用 extends 擴展
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}
// Dog 有 name 和 breed 兩個欄位

// type 用 & 組合
type Animal = {
  name: string;
};
type Dog = Animal & {
  breed: string;
};
// Dog 同樣有 name 和 breed 兩個欄位
Code language: PHP (php)

翻譯:兩種方式結果一樣,都是「Dog 繼承了 Animal 的欄位,再加上自己的」。只是寫法不同。

差異二:type 能做的事比較多

// type 可以定義聯合型別,interface 不行
type Status = "active" | "inactive" | "banned";

// type 可以定義元組,interface 不行
type Coordinate = [number, number];

// type 可以用基本型別,interface 不行
type UserID = string | number;
Code language: JavaScript (javascript)

什麼時候用哪個?

場景 建議用 理由
定義物件結構 interface 最常見的慣例
聯合型別 type interface 做不到
基本型別別名 type interface 做不到
不確定 都行 結果幾乎一樣

結論:AI 產出的程式碼兩種都會用。看到 interfacetype 定義物件時,讀法一模一樣,不用擔心。


常見變化

AI 產出的程式碼中,你常會遇到以下幾種寫法:

變化 1:巢狀物件型別

interface User {
  name: string;
  address: {          // address 本身也是一個物件
    city: string;
    zipCode: string;
  };
}

const alice: User = {
  name: "Alice",
  address: {
    city: "Taipei",
    zipCode: "100",
  },
};
Code language: PHP (php)

翻譯:「User 有一個 address 欄位,而 address 裡面又有 city 和 zipCode。」就是物件裡面包物件。

也可以拆開寫(比較常見的做法):

interface Address {
  city: string;
  zipCode: string;
}

interface User {
  name: string;
  address: Address;   // 直接引用另一個 interface
}
Code language: PHP (php)

翻譯:「先定義 Address 結構,再讓 User 的 address 欄位使用 Address 結構。」效果一模一樣,但更好讀。

變化 2:函式型別的定義

// 定義一個函式的型別
type GreetFn = (name: string) => string;

const greet: GreetFn = (name) => `Hello, ${name}`;
Code language: JavaScript (javascript)

翻譯:「GreetFn 是一個函式型別:接收一個 string 參數,回傳一個 string。greet 這個函式必須符合這個型別。」

用 interface 也可以寫函式型別(但比較少見):

interface GreetFn {
  (name: string): string;
}
Code language: PHP (php)

翻譯:效果一樣,但用 type 寫更直覺,所以 AI 通常會用 type 來定義函式型別。

變化 3:多個 interface 組合

interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

interface User extends HasName, HasAge {
  email: string;
}
// User 有 name、age、email 三個欄位
Code language: PHP (php)

翻譯:「User 同時繼承了 HasName 和 HasAge,再加上自己的 email。」一個 interface 可以繼承多個。


必看懂 vs 知道就好

必看懂(會一直出現)

  • interface User { name: string; } — 定義物件結構
  • email?: string — 可選欄位
  • readonly id: number — 唯讀欄位
  • type UserID = string | number — 型別別名
  • extends& — 擴展或組合型別

知道就好(遇到再查)

  • interface 的 declaration merging(同名 interface 會自動合併)
  • 泛型 interface interface Box<T> { value: T }(第 4 篇會教)
  • keyoftypeof、mapped types 等進階型別操作
  • Partial<T>Required<T>Pick<T, K> 等工具型別

Vibe Coder 檢查點

看到 AI 產出的 interfacetype 時,確認這些事:

  • [ ] 每個欄位的型別合理嗎?(例如 age 應該是 number,不是 string
  • [ ] 可選欄位 ? 的設定合理嗎?(真的可以不填的才應該是可選)
  • [ ] 必填欄位有沒有漏?(例如建立 User 通常需要 name)
  • [ ] 巢狀物件有沒有拆出獨立的 interface?(拆開比較好讀)
  • [ ] readonly 用在正確的地方嗎?(id、建立時間等不該被改的欄位)

本篇重點回顧

語法 意思 範例
interface 定義物件結構 interface User { name: string }
type 幫型別取名字 type ID = string \| number
? 可選欄位 email?: string
readonly 唯讀,不能改 readonly id: number
extends interface 的擴展 interface Dog extends Animal
& type 的組合 type Dog = Animal & { breed: string }

下一篇,我們會看 TypeScript 的泛型 <T> — AI 超愛用的型別參數化寫法。

進階測驗:interface 與 type — 定義物件的形狀

測驗目標:驗證你是否能在實際情境中應用所學。
共 5 題,包含情境題與錯誤診斷題。

1. AI 幫你生成了一個 API 回傳的使用者資料型別,你需要決定哪些欄位該設為可選。以下哪個設計最合理? 情境題

情境:你的應用程式有一個使用者註冊頁面,nameemail 是必填欄位,phonebio 是選填欄位。

  • A. 全部欄位都設為必填,之後再用空字串填入選填欄位
  • B. 全部欄位都設為可選 ?,保持最大彈性
  • C. nameemail 設為必填,phone?bio? 設為可選
  • D. 用 readonly 標記必填欄位,用 ? 標記選填欄位

2. 你正在檢視 AI 產出的程式碼,看到以下型別定義。你需要選擇用 interface 還是 type 來定義 Status情境題

// 需要定義一個 Status 型別,只能是以下三個值之一: // “pending” | “approved” | “rejected”
  • A. 用 interface Status { value: "pending" | "approved" | "rejected" }
  • B. 用 type Status = "pending" | "approved" | "rejected"
  • C. 用 interfacetype 都可以,效果一模一樣
  • D. 不需要定義型別,直接用 string 就好

3. 你的專案中有多個實體(Product、Order、User)都需要 idcreatedAt 欄位。AI 建議用以下哪種方式來避免重複定義? 情境題

  • A. 定義一個 interface BaseEntity { readonly id: number; readonly createdAt: string; },讓其他 interface 用 extends 繼承
  • B. 在每個 interface 裡面分別寫 idcreatedAt
  • C. 用 type 的聯合型別 | 把三個實體合在一起
  • D. 把 idcreatedAt 定義為全域變數

4. 以下程式碼出現了 TypeScript 錯誤,最可能的原因是什麼? 錯誤診斷

interface Product { name: string; price: number; description?: string; } const item: Product = { name: “Laptop”, description: “A powerful laptop”, }; // TypeScript 報錯!
  • A. description 是可選欄位,不應該提供值
  • B. 缺少必填欄位 price,它沒有 ? 所以一定要提供
  • C. const 不能搭配 interface 使用,要改用 let
  • D. description 的值應該是 number 而非 string

5. 以下程式碼中,AI 生成了一段看似合理的操作,但有一行會被 TypeScript 擋下。請找出問題所在。 錯誤診斷

interface Order { readonly id: number; readonly createdAt: string; status: string; total: number; } const order: Order = { id: 1001, createdAt: “2024-01-15”, status: “pending”, total: 599, }; order.status = “shipped”; // 第 1 行 order.total = 499; // 第 2 行 order.createdAt = “2024-02-01”; // 第 3 行 order.id = 1002; // 第 4 行
  • A. 只有第 4 行有錯,id 是唯讀的
  • B. 第 1 行和第 2 行有錯,因為物件建立後不能修改任何屬性
  • C. 只有第 3 行有錯,createdAt 是唯讀的
  • D. 第 3 行和第 4 行都有錯,createdAtid 都是 readonly 不能修改

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *