---
# System prepended metadata

title: "\U0001F3C5 Day 13 - type 泛型"
tags: [TypeScript 直播班-2025]

---

#  🏅 Day 13 - type 泛型

泛型也很常用在 type 型別別名上，如以下範例：


```tsx=
// 代入 Passthrough<T> 裡面的 T 為什麼樣的型別
// 賦值後的 T 就會成為該型別
type Passthrough<T> = T;

// 使用 Passthrough 定義不同型別的變數
let numberValue: Passthrough<number> = 42;

// 引發錯誤，型別衝突
let numberValue2: Passthrough<number> = "42";  

let stringValue: Passthrough<string> = "Hello, world!";
let booleanValue: Passthrough<boolean> = true;
let arrayValue: Passthrough<number[]> = [1, 2, 3, 4, 5];

// 使用這些變數
console.log(numberValue); // 輸出: 42
console.log(stringValue); // 輸出: "Hello, world!"
console.log(booleanValue); // 輸出: true
console.log(arrayValue);   // 輸出: [1, 2, 3, 4, 5]
```

當使用型別別名泛型時，TypeScript 不會自動去做型別推斷。所以需要在使用時，明確指定型別參數，例如： **`<number>`** 或 **`<string>`**。


## **情境一：身高範例**

通常情況下，我們可能會這樣定義一個表示人的型別，其中包含身高屬性，並設定為 **`number`** 型別：

```tsx=
type Person = {
  height: number;
};

const mike: Person = {
  height: 180,
};
```

以這例子中，**`mike`** 的身高被設定為一個數字 180。

### **使用泛型的時機**

有可能在專案中，會同時需要用數字，或中文字例如「一百八十公分」來呈現，此時就需要比較靈活的型別定義。這樣泛型就派上用場了。

我們可以這樣定義一個泛型型別名：

```tsx=
type FlexiblePerson<T> = {
  height: T;
};

const mike1: FlexiblePerson<number> = {
  height: 180,
};

const mike2: FlexiblePerson<string> = {
  height: '一百八十公分',
};

```

在 **`mike1`** 的例子中，身高是以數字形式表示的，而在 **`mike2`** 中，身高則是以字串來呈現。


## 情境二：水果物件表

以下是設計一個儲存商品列表資訊的功能。想要用 **`GenericValObj`** 來建立一個物件，該物件將以商品名稱作為 `key`，並以商品價格作為值。

```tsx=
// 新增泛型物件型別
// 屬性 key 必須為字串形式，可以無限制的增加
type GenericValObj<T> = {
    [key: string]: T;
}

// 如果 key 值有限定的內容，也可以透過 type 來進行組合
type BodyKey = "height" | "weight" | "bloodPressure"
type BodyValue<T> = {
    [K in BodyKey]: T
}

// 定義 productPrices 儲存商品價格，value 為 number 型別
const productPrices: GenericValObj<number> = {
    "apple": 1.99,
    "banana": 0.99,
    "cherry": 2.49,
};

// 新增更多商品價格
productPrices["grape"] = 1.79;
// productPrices["orange"] = "三美元"; // 會出錯，因為泛型設定的型別參數為 number

// 定義一個 productDescriptions 物件來儲存商品描述，value 為 string 型別
const productDescriptions: GenericValObj<string> = {
    "apple": "一個美味的水果",
    "banana": "一個黃色的水果",
    "cherry": "一個比較小又紅的水果",
};

// 新增更多商品描述
productDescriptions["grape"] = "A small purple fruit";
```



## **情境三：API  response 包裝**

如果你正在開發一個應用程式，需要與多個不同的 API 進行交互。這些 API 回傳不同型別的數據

例如：

1. 一些回傳數字類型的數據（如使用者的年齡）
2. 另一些則回傳字符串類型的數據（如使用者的姓名）

如果想要用統一方法來包裝這些 API response，可以這樣寫：
```tsx
type ApiResponse<T> = {
    data: T;
    status: string;
}

// 使用範例
const ageResponse: ApiResponse<number> = { data: 30, status: "success" };
const nameResponse: ApiResponse<string> = { data: "Alice", status: "success" };
```

如果資料會需要管理 response 回傳成功或失敗的話，可以這樣寫：

```tsx=
// 定義 User 介面
interface User {
    name: string;
    age: number;
}

// 定義 ApiResponse 泛型型別
type ApiResponse<T> = {
    success: boolean;
    message: string | null;
    data: T | null;
}

// 使用 User 介面的 ApiResponse
const userResponse: ApiResponse<User> = {
    success: true,
    message: null,
    data: { name: "Alice", age: 30 }
};

// 用於失敗的 ApiResponse，沒資料
const errorResponse: ApiResponse<null> = {
    success: false,
    message: "Error retrieving data",
    data: null
};
```


## **情境四：網頁版本的 MP3 播放器**

假設你正在開發一個網頁版的 MP3 播放器。這個播放器需要許多不同的設定選項，例如音量控制、播放模式（例如隨機播放）、使用者設定等。

來試著用泛型來進行規劃：

```tsx=
// 定義設定選項的 type 泛型
type ConfigOption<T> = {
    key: string;
    value: T;
    description: string;
    category: string;
}

// 使用範例
const volumeConfig: ConfigOption<number> = {
    key: "volume",
    value: 75,
    description: "控制播放器的音量大小",
    category: "音效設定"
};

const shuffleModeConfig: ConfigOption<boolean> = {
    key: "shuffleMode",
    value: true,
    description: "啟用/停用隨機播放模式",
    category: "播放控制"
};

const usernameConfig: ConfigOption<string> = {
    key: "username",
    value: "user123",
    description: "用戶的顯示名稱",
    category: "用戶設定"
};

```


## 開發題

### 1. 資料庫重構優化

請嘗試將 `UserEntry` 和 `ProductEntry` 重構，合併成一個自訂的 `type`，並使用泛型來進行優化

```tsx=
// 定義 UserEntry 和 ProductEntry 型別
type UserEntry = {
    id: string;
    data: {
        name: string;
        age: number;
    };
}

type ProductEntry = {
    id: string;
    data: {
        title: string;
        price: number;
    };
}

// 建立 UserEntry 和 ProductEntry 的函式
function createUserEntry(id: string, data: { name: string; age: number; }): UserEntry {
    return { id, data };
}

function createProductEntry(id: string, data: { title: string; price: number; }): ProductEntry {
    return { id, data };
}

// 使用函式來建立 UserEntry 和 ProductEntry
const userEntry = createUserEntry("user1", { name: "John Doe", age: 30 });
const productEntry = createProductEntry("product1", { title: "Apple iPhone 13", price: 799 });
```


### 2. 外送訂單管理系統

你正在開發一個能夠管理外送訂單和餐點的系統，參考下面的部分程式碼，盡可能使用泛型和前面所學的其它技巧來優化程式碼內容

```tsx=
// 定義食物和飲料型別
type Food = {
  id: string;
  name: string;
  price: number;
}

type Drink = {
  id: string;
  name: string;
  sugarLevel: string; // 只包含 "standard", "half", "no"
  price: number;
}

// 定義訂單型別
type Order = {
  id: string;
  customerName: string;
  item: Food | Drink;
}

// 定義一個 OperationResult 代表完成一次操作後的回傳格式
type OperationResult<T> = {
  success: boolean;
  data: T;
  message: string;
}

const foods: Food[] = []
const drinks: Drink[] = []
const orders: Order[] = []

function addFood(food: Food): OperationResult<Food> {
  foods.push(food);
  return {
    success: true,
    data: food,
    message: "成功加入食物",
  };
}

function addDrink(drink: Drink): OperationResult<Drink> {
  drinks.push(drink);
  return {
    success: true,
    data: drink,
    message: "成功加入飲料",
  };
}

function addOrder(order: Order): OperationResult<Order> {
  orders.push(order);
  return {
    success: true,
    data: order,
    message: "成功加入訂單",
  };
}

// 隨機生成 ID
function genId(prefix: string): string {
  return `${prefix}-${Math.random().toString(36).slice(2, 8)}`;
}

function createFood(name: string, price: number): Food {
  return {
    id: genId("food"),
    name,
    price
  }
}

function createDrink(name: string, price: number, sugarLevel: string): Drink {
  return {
    id: genId("drink"),
    name,
    sugarLevel,
    price
  }
}

function createOrder(customerName: string, item: Food | Drink): Order {
  return {
    id: genId("order"),
    customerName,
    item,
  }
}

// 測試函式
const friedChicken = createFood("friedChicken", 100)
const cola = createDrink("cola", 50, "standard")


console.log(addFood(friedChicken))
console.log(addDrink(cola))
console.log(addOrder(createOrder("Jack", friedChicken)))
```


<!-- 解答：  

1. 
type DataEntry<T> = {
  id: string;
  data: T
}

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

type Product = {
  title: string;
  price: number;
}

// 建立 UserEntry 和 ProductEntry 的函式
function createUserEntry(id: string, data: { name: string; age: number; }): DataEntry<User> {
    return { id, data };
}

function createProductEntry(id: string, data: { title: string; price: number; }): DataEntry<Product> {
    return { id, data };
}

2.
// 定義食物和飲料型別
type Product = {
  id: string;
  name: string;
  price: number;
}

type SugarLevel = "standard" | "half" | "no"

interface Food extends Product {}

interface Drink extends Product {
  sugarLevel: SugarLevel
}

// 定義訂單型別
type Order = {
  id: string;
  customerName: string;
  item: Food | Drink;
}

// 定義一個 OperationResult 代表完成一次操作後的回傳格式
type OperationResult<T> = {
  success: boolean;
  data: T;
  message: string;
}

const foods: Food[] = []
const drinks: Drink[] = []
const orders: Order[] = []

type addObject<T> = (obj: T) => OperationResult<T>

function getAddObjectFunc<T> (objList: T[], message: string): addObject<T> {
  return function (obj: T) {
    objList.push(obj)
    return {
      success: true,
      data: obj,
      message
    }
  }
}

const addFood = getAddObjectFunc<Food>(foods, "成功加入食物")
const addDrink = getAddObjectFunc<Drink>(drinks, "成功加入飲料")
const addOrder = getAddObjectFunc<Order>(orders, "成功加入訂單")


// 隨機生成 ID
function genId(prefix: string): string {
  return `${prefix}-${Math.random().toString(36).slice(2, 8)}`;
}

function createFood(name: string, price: number): Food {
  return {
    id: genId("food"),
    name,
    price
  }
}

function createDrink(name: string, price: number, sugarLevel: SugarLevel): Drink {
  return {
    id: genId("drink"),
    name,
    sugarLevel,
    price
  }
}

function createOrder(customerName: string, item: Food | Drink): Order {
  return {
    id: genId("order"),
    customerName,
    item,
  }
}

// 測試函式
const friedChicken = createFood("friedChicken", 100)
const cola = createDrink("cola", 50, "standard")


console.log(addFood(friedChicken))
console.log(addDrink(cola))
console.log(addOrder(createOrder("Jack", friedChicken)))
-->


