跳至主要內容
Skip to content

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) // 30

1.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'] = '新的' // OK

4.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) // Taipei

5.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] // boolean

7.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常量斷言深度唯讀

> **最佳實踐**:

  • 使用 interfacetype 定義可重用的物件類型
  • 適當使用 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 座標

延伸閱讀與資源