TypeScript 物件型別 (Object Types)
在 TypeScript 中,物件型別用來描述物件的結構,包括屬性的名稱和型別。這是 TypeScript 型別系統的核心概念之一。
物件型別標註
內聯物件型別
直接在變數後面標註物件的結構:
let user: { name: string; age: number } = {
name: '小明',
age: 25,
};
// 存取屬性
console.log(user.name); // 小明
console.log(user.age); // 25
函式參數的物件型別
function printUser(user: { name: string; age: number }): void {
console.log(`${user.name}, ${user.age} 歲`);
}
printUser({ name: '小明', age: 25 });
可選屬性 (Optional Properties)
使用 ? 標記可選屬性:
let user: {
name: string;
age: number;
email?: string; // 可選屬性
} = {
name: '小明',
age: 25,
// email 可以省略
};
// 存取可選屬性時可能是 undefined
function printEmail(user: { email?: string }) {
if (user.email) {
console.log(user.email);
} else {
console.log('沒有提供 email');
}
}
唯讀屬性 (Readonly Properties)
使用 readonly 關鍵字標記唯讀屬性:
let point: {
readonly x: number;
readonly y: number;
} = { x: 10, y: 20 };
console.log(point.x); // 10
// point.x = 30; // 錯誤:無法指派給 'x',因為它是唯讀屬性
readonly 只會在編譯時期檢查,不會影響執行時的行為。索引簽名 (Index Signatures)
當物件可以有動態的屬性名稱時,使用索引簽名:
// 字串索引簽名
let dictionary: { [key: string]: string } = {
apple: '蘋果',
banana: '香蕉',
orange: '橘子',
};
dictionary['grape'] = '葡萄'; // OK
console.log(dictionary['apple']); // 蘋果
// 數字索引簽名
let list: { [index: number]: string } = ['a', 'b', 'c'];
console.log(list[0]); // a
結合固定屬性和索引簽名
interface Config {
name: string;
version: number;
[key: string]: string | number; // 其他屬性可以是 string 或 number
}
let config: Config = {
name: 'my-app',
version: 1,
author: '小明',
port: 3000,
};
索引簽名的值型別必須能包含所有固定屬性的型別。
物件型別的結構化型別 (Structural Typing)
TypeScript 使用結構化型別系統,只要結構相符就視為相同型別:
interface Point {
x: number;
y: number;
}
function printPoint(point: Point) {
console.log(`(${point.x}, ${point.y})`);
}
// 不需要明確宣告實作 Point
let myPoint = { x: 10, y: 20, z: 30 }; // 多了 z 屬性
printPoint(myPoint); // OK,因為有 x 和 y
// 但直接傳入字面量時會檢查多餘屬性
// printPoint({ x: 10, y: 20, z: 30 }); // 錯誤:多餘的屬性 'z'
交集型別 (Intersection Types)
使用 & 組合多個物件型別:
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;
let person: Person = {
name: '小明',
age: 25,
};
巢狀物件型別
interface Address {
city: string;
street: string;
zipCode: string;
}
interface User {
name: string;
age: number;
address: Address;
}
let user: User = {
name: '小明',
age: 25,
address: {
city: '台北市',
street: '信義路',
zipCode: '110',
},
};
console.log(user.address.city); // 台北市
Record 工具型別
Record<K, V> 是建立物件型別的快捷方式:
// 相當於 { [key: string]: number }
type StringToNumber = Record<string, number>;
let scores: StringToNumber = {
math: 90,
english: 85,
science: 92,
};
// 使用聯合型別作為 key
type Fruit = 'apple' | 'banana' | 'orange';
type FruitInventory = Record<Fruit, number>;
let inventory: FruitInventory = {
apple: 10,
banana: 15,
orange: 8,
};
物件的解構與型別
// 解構時標註型別
function printUser({ name, age }: { name: string; age: number }) {
console.log(`${name}, ${age} 歲`);
}
// 解構並重新命名
function printCoordinate({ x: xPos, y: yPos }: { x: number; y: number }) {
console.log(`x 座標: ${xPos}, y 座標: ${yPos}`);
}
// 解構時設定預設值
function createUser({ name, age = 18 }: { name: string; age?: number }) {
return { name, age };
}
多餘屬性檢查 (Excess Property Checks)
直接傳入物件字面量時,TypeScript 會檢查多餘的屬性:
interface Point {
x: number;
y: number;
}
// 直接傳入字面量會報錯
// let p: Point = { x: 10, y: 20, z: 30 }; // 錯誤
// 透過變數則不會
let obj = { x: 10, y: 20, z: 30 };
let p: Point = obj; // OK
// 使用型別斷言
let p2 = { x: 10, y: 20, z: 30 } as Point; // OK
Object vs object vs
這三個型別有不同的含義:
// Object:幾乎所有值 (除了 null 和 undefined)
let a: Object = 'hello'; // OK
let b: Object = 123; // OK
let c: Object = {}; // OK
// object:非原始型別的值
let d: object = {}; // OK
let e: object = []; // OK
// let f: object = "hello"; // 錯誤
// {}:空物件型別,可以接受任何值 (除了 null 和 undefined)
let g: {} = 'hello'; // OK
let h: {} = 123; // OK
// 但不能存取任何屬性
// g.length; // 錯誤
建議避免使用
Object、object 和 {},改用更具體的介面或型別。實用範例
API 回應物件
interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
timestamp: number;
}
interface User {
id: number;
name: string;
email: string;
}
function handleResponse(response: ApiResponse<User>) {
if (response.success) {
console.log(response.data.name);
} else {
console.error(response.error);
}
}
設定物件
interface AppConfig {
readonly appName: string;
version: string;
settings: {
theme: 'light' | 'dark';
language: string;
notifications: boolean;
};
features?: {
[key: string]: boolean;
};
}
const config: AppConfig = {
appName: 'My App',
version: '1.0.0',
settings: {
theme: 'dark',
language: 'zh-TW',
notifications: true,
},
features: {
newDashboard: true,
experimentalFeature: false,
},
};