TypeScript 物件、陣列與元組
物件是 JavaScript 最常用的資料結構。本篇將深入介紹如何在 TypeScript 中定義物件類型。
一、 物件類型基礎
1.1 行內類型定義
typescript
// 直接定義物件類型
const user: { name: string; age: number } = {
name: 'John',
age: 30,
}
// 存取屬性
console.log(user.name) // John
console.log(user.age) // 301.2 類型推論
typescript
// TypeScript 自動推論
const user = {
name: 'John',
age: 30,
}
// 類型:{ name: string; age: number }
// 注意:推論結果是可變的
user.name = 'Jane' // OK
user.age = 25 // OK二、 可選屬性(Optional Properties)
2.1 使用 ? 標記
typescript
// email 是可選的
const user: { name: string; age: number; email?: string } = {
name: 'John',
age: 30,
// email 可以不給
}
// 也可以給
const user2: { name: string; age: number; email?: string } = {
name: 'Jane',
age: 25,
email: 'jane@example.com',
}2.2 存取可選屬性
typescript
interface User {
name: string
email?: string
}
function getEmail(user: User): string {
// email 可能是 undefined
// return user.email // 可能是 undefined
// 使用預設值
return user.email ?? 'No email'
// 或檢查後使用
if (user.email) {
return user.email
}
return 'No email'
}三、 唯讀屬性(Readonly Properties)
3.1 使用 readonly
typescript
interface User {
readonly id: number
name: string
}
const user: User = {
id: 1,
name: 'John',
}
user.name = 'Jane' // OK
// user.id = 2 // 錯誤!id 是唯讀的3.2 Readonly 工具類型
typescript
interface User {
id: number
name: string
}
// 所有屬性變成唯讀
const user: Readonly<User> = {
id: 1,
name: 'John',
}
// user.id = 2 // 錯誤!
// user.name = 'Jane' // 錯誤!3.3 const 斷言
typescript
// as const 讓物件變成深度唯讀
const config = {
api: 'https://api.example.com',
timeout: 5000,
} as const
// config.api = '...' // 錯誤!四、 索引簽名(Index Signatures)
4.1 動態屬性名
typescript
// 允許任意字串鍵
interface Dictionary {
[key: string]: string
}
const dict: Dictionary = {
hello: '你好',
world: '世界',
typescript: 'TypeScript',
}
dict['new'] = '新的' // OK4.2 混合固定與動態屬性
typescript
interface User {
id: number
name: string
[key: string]: string | number // 其他屬性
}
const user: User = {
id: 1,
name: 'John',
email: 'john@example.com',
age: 30,
}五、 巢狀物件
5.1 多層結構
typescript
interface Address {
street: string
city: string
country: string
}
interface User {
name: string
address: Address
}
const user: User = {
name: 'John',
address: {
street: '123 Main St',
city: 'Taipei',
country: 'Taiwan',
},
}
console.log(user.address.city) // Taipei5.2 可選巢狀
typescript
interface User {
name: string
address?: {
city: string
}
}
const user: User = { name: 'John' }
// 安全存取
console.log(user.address?.city) // undefined六、 陣列與物件
6.1 物件陣列
typescript
interface User {
id: number
name: string
}
const users: User[] = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
]
// 操作陣列
users.push({ id: 3, name: 'Bob' })
const names = users.map((user) => user.name)6.2 陣列屬性
typescript
interface User {
name: string
hobbies: string[]
scores: number[]
}
const user: User = {
name: 'John',
hobbies: ['reading', 'coding'],
scores: [95, 87, 92],
}七、 元組深入
7.1 復習:基本元組
typescript
// 固定長度和類型
const tuple: [string, number, boolean] = ['John', 30, true]
const name = tuple[0] // string
const age = tuple[1] // number
const active = tuple[2] // boolean7.2 解構賦值
typescript
const tuple: [string, number] = ['John', 30]
// 解構
const [name, age] = tuple
console.log(name) // John
console.log(age) // 30
// 常見於函式回傳
function useState<T>(initial: T): [T, (v: T) => void] {
let state = initial
const setState = (value: T) => {
state = value
}
return [state, setState]
}
const [count, setCount] = useState(0)7.3 剩餘元素
typescript
// 前面固定,後面任意
type StringNumberBooleans = [string, number, ...boolean[]]
const a: StringNumberBooleans = ['hello', 1]
const b: StringNumberBooleans = ['hello', 1, true]
const c: StringNumberBooleans = ['hello', 1, true, false, true]7.4 元組 vs 陣列
| 特性 | 元組 | 陣列 |
|---|---|---|
| 長度 | 固定 | 可變 |
| 每個位置類型 | 可不同 | 相同 |
| 適用場景 | 已知結構 | 集合資料 |
typescript
// 元組:結構已知
const point: [number, number] = [10, 20]
const response: [number, string, object] = [200, 'OK', { data: [] }]
// 陣列:集合
const numbers: number[] = [1, 2, 3, 4, 5]
const names: string[] = ['John', 'Jane', 'Bob']八、 實用範例
8.1 API 回應
typescript
interface ApiResponse<T> {
success: boolean
data: T
error?: string
timestamp: number
}
interface User {
id: number
name: string
email: string
}
const response: ApiResponse<User> = {
success: true,
data: {
id: 1,
name: 'John',
email: 'john@example.com',
},
timestamp: Date.now(),
}8.2 設定物件
typescript
interface Config {
readonly apiUrl: string
readonly timeout: number
features: {
darkMode: boolean
notifications: boolean
}
[key: string]: unknown
}
const config: Config = {
apiUrl: 'https://api.example.com',
timeout: 5000,
features: {
darkMode: true,
notifications: false,
},
customSetting: 'value',
}總結
| 語法 | 用途 | 範例 |
|---|---|---|
? | 可選屬性 | email?: string |
readonly | 唯讀屬性 | readonly id: number |
[key: string] | 索引簽名 | 動態屬性 |
?. | 可選鏈 | user.address?.city |
as const | 常量斷言 | 深度唯讀 |
> **最佳實踐**:
- 使用
interface或type定義可重用的物件類型 - 適當使用
readonly保護不應變更的資料 - 善用可選屬性處理不確定的欄位
進階挑戰
定義以下資料結構的類型:
typescript
// 練習 1:使用者資料
const user = {
id: 1,
username: 'john_doe',
email: 'john@example.com',
profile: {
avatar: 'https://example.com/avatar.jpg',
bio: 'Hello!',
},
roles: ['user', 'admin'],
}
// 練習 2:商品資料(價格唯讀、折扣可選)
const product = {
id: 101,
name: 'TypeScript Book',
price: 599,
discount: 0.1,
tags: ['programming', 'typescript'],
}
// 練習 3:座標元組
const point = [10, 20, 30] // 提示:3D 座標