iT邦幫忙

2023 iThome 鐵人賽

DAY 26
0
Modern Web

TypeScript 魔法 - 喚醒你的程式碼靈感系列 第 26

Day26 - 泛型(Generics)下篇 - 泛型的多種姿態

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20231011/20152047AQMOh8O65z.png

在前一篇文章中,我們介紹了泛型的基本概念和它所提供的靈活性。今天,我們將深入探討泛型的多種應用方式,包括多個型別參數的泛型函式、泛型約束、泛型介面和泛型類別。讓我們一同探索這些進階主題。

多個型別參數

泛型函式不僅僅可以有一個型別參數,還可以有多個型別參數。

function pair<T, U>(first: T, second: U): [U, T] {
  return [second, first];
}

const result = pair(42, 'Hello');
console.log(result); // ['Hello', 42]

泛型函式

泛型函式是使用泛型型別的函式,它們使我們能夠建立通用的、可重用的函式,處理不同型別的輸入資料。

非泛型函式:

function merge(objA: object, objB: object) {
  return { ...objA, ...objB };
}

const mergedObj = merge({ name: '肉鬆' }, { age: 30 });
mergedObj.name; // TypeScript 報錯
mergedObj.age; // TypeScript 報錯

儘管我們知道 mergedObjnameage 這兩個屬性,但 TypeScript 產生錯誤,這是因為它不提供任何有關返回物件的型別資訊。因此,在使用這個函式的結果時,我們無法確定返回的物件具有哪些屬性。

泛型函式:

function merge<T, U>(objA: T, objB: U) {
  return { ...objA, ...objB };
}

const mergedObj = merge({ name: '肉鬆' }, { age: 30 });
console.log(mergedObj.name); // 肉鬆
console.log(mergedObj.age); // 30

https://ithelp.ithome.com.tw/upload/images/20231011/201520479DqvCqr6ps.png

在函式名稱後面使用了尖括號 <>,引入了兩個泛型參數 TUT 表示型別,而 U 表示另一個型別(這邊的 TU 可以自己命名)。最後函式的返回型別被定義為 TU 的交集(T & U),這意味著返回的物件將包含 TU 兩種型別的屬性。

當然,我們也可以明確指定泛型參數的型別,但這裡我們不需要這麼做,因為 TypeScript 可以根據上下文進行型別推斷,使程式碼更簡潔。

function merge<T, U>(objA: T, objB: U) {
  return { ...objA, ...objB };
}

const mergedObj = merge<{ name: string }, { age: number }>(
  { name: '肉鬆' },
  { age: 30 }
);
console.log(mergedObj.name); // 肉鬆
console.log(mergedObj.age); // 30

泛型約束

泛型約束是一種限制泛型型別的方法,使其只能是某些型別的子集。

function merge<T, U>(objA: T, objB: U) {
  return { ...objA, ...objB };
}

const mergedObj = merge({ name: "肉鬆" }, 30); // 通過,TypeScript 不會報錯

在上面的範例中,我們將原本傳入 objB 的物件型別更改為 number 型別。在 JavaScript 中,這樣的操作是合法的,因為 JavaScript 是一種動態型別語言,允許在執行時更改變數的型別。因此,30(一個數字)可以成功分配給 objB

實際上,我們不關心具體的型別,也不關心泛型參數 TU 提供的物件結構。 我們真正關心的是,這兩個參數,也就是型別 TU,應該要是物件,而不僅僅是任何型別。

讓我們改寫程式碼,使用 extends 約束型別:

function merge<T extends object, U extends object>(objA: T, objB: U) {
  return { ...objA, ...objB };
}

const mergedObj1 = merge({ name: '肉鬆' }, 30);
// TypeScript 報錯,類型 'number' 的引數不可指派給類型 'object' 的參數

const mergedObj2 = merge({ name: '肉鬆' }, { age: 30 }); // 通過

泛型介面

泛型介面是一種定義可以處理多種不同型別資料的規範。使我們可以建立具有可變型別的自訂型別。

interface Pair<K, V> {
  key: K;
  value: V;
}

// 使用泛型介面來建立具體的實例
const numberPair: Pair<number, string> = { key: 1, value: 'one' };
const stringPair: Pair<string, boolean> = { key: 'isTrue', value: true };

// 定義一個函式,根據給定的 Pair 物件返回 value
function getValue<T, U>(pair: Pair<T, U>) {
  return pair.value;
}

const result1 = getValue(numberPair);
const result2 = getValue(stringPair);

console.log(result1); // 'one'
console.log(result2); // true

在上面的範例中,我們定義了一個 Pair 介面,表示具有 keyvalue 的對應關係。然後,我們建立了兩個不同型別的 Pair 實例 numberPairstringPair。最後,我們定義了一個 getValue 函式,它接受一個 Pair 物件,並返回 value

泛型類別

泛型類別允許我們建立通用的類別定義,允許我們在實例化類別時指定不同的型別參數。

class Stack<T> {
  items: T[] = [];

  push(item: T) {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);

const stringStack = new Stack<string>();
stringStack.push('傑尼龜');
stringStack.push('肉鬆');

console.log(numberStack.pop()); // 3
console.log(stringStack.pop()); // '肉鬆'

在上面的範例中,我們定義了一個 Stack<T> 泛型類別,其中 T 是型別參數,表示堆疊可以處理的元素的型別。然後,我們可以使用 Stack<number>Stack<string> 來分別建立處理數字和字串的堆疊實例。

本日重點

  1. 泛型函式可以有多個型別參數,型別參數在函式內部用於定義參數和返回型別。
  2. 泛型約束是一種限制泛型型別的方法,確保它只能是某些型別的子集。
  3. 泛型介面定義了可以處理多種不同型別資料的約定,建立具有可變型別的自訂型別。
  4. 泛型類別允許在實例化類別時指定不同的型別參數,建立通用的類別定義,以處理不同型別的值。

參考


上一篇
Day25 - 泛型(Generics)上篇 - 讓程式碼變得更通用!
下一篇
Day27 - 通用又實用的型別 - Partial & Readonly
系列文
TypeScript 魔法 - 喚醒你的程式碼靈感30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言