TypeScript基礎(二)泛型的應用

語言: CN / TW / HK

theme: channing-cyan

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第4天,點擊查看活動詳情

泛型函數

泛型: 是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。

定義泛型函數

正常聲明一個變量的時候,像是下面這樣。 ts const a: string = "kong";泛型,是在使用的時候才會指定類型(一般用大寫的T做變量,Type的簡寫,用其他的也都沒問題),如下:

ts function fn1<T>(value: T): T { return value; } fn1<string>("kong"); fn1(1); 上面的函數fn1添加了類型變量 T,T能夠捕獲到用户傳入的參數類型,然後再根據T做函數返回值的類型。

調用的時候可以<>手動設定類型,也可以不設置讓ts自動進行類型推導。

  • 默認類型

聲明函數的時候,可以傳給T一個默認類型。調用函數的時候,不傳遞類型就是默認類型。

ts function fn1<T = number>(value: T): T { return value; }

  • 多個類型參數

定義泛型的時候,可以一次性定義多個類型參數

ts function swap<T, U>(arr: [T, U]): [U, T] { return [arr[1], arr[0]] } swap([100, "haha"]); // ["haha", 100]

類型約束

在函數內部使用泛型的變量的時候,由於還不知道它屬於哪種類型,所以不能隨意操作變量的屬性和方法。

如下: value.length 會出現錯誤信息 類型T上不存在屬性length

ts function fn2<T>(value: T){ value.length // 錯誤:類型T上不存在屬性length }

這時候就可以使用 類型約束,關鍵字 extends

```ts interface HasLength { length: number }

function fn3(value: T) { value.length } ```

上述代碼,讓泛型T繼承自Haslength接口,表明傳入的參數要遵守 HasLength接口的規則,也就是必須要含有length屬性。

也可以直接這樣繼承: ts function fn3<T extends {length: number}>(value: T) { value.length }

此時,如果傳入不包含length屬性的參數,就會在編譯階段提示錯誤:

ts fn3([1,2,3]); // 正確 fn3("KONG"); // 正確 fn3(100); // 錯誤

提示錯誤: 類型number的參數不能賦值給類型HasLength的參數。

使用泛型編寫一個好的函數要注意的點

  1. 可能的情況下,使用類型參數本身,而不是對其進行約束
  2. 儘可能少的使用類型參數
  3. 如果一個類型的參數只出現在一個地方,請重新考慮是否真的需要它

泛型接口

定義一個標準的接口,應該像下面這樣:

ts interface Person { name: string, age: number } const p1: Person = { name: "tom", age: 18 }

  • 簡單使用

上面接口裏面的類型是固定寫死的,如果需要由用户傳入接口的類型,那就需要使用泛型來定義接口:

ts interface Person2<T1, T2> { name: T1, age: T2, } const p2: Person2<string, number> = { name: "jack", age: 18 }

上面的例子是在使用的時候傳入了類型,也可以在定義接口的時候給泛型默認值:

ts interface Person2<T1 = string, T2 = number> { name: T1, age: T2, }

  • 使用泛型接口的方式定義函數需要符合的形狀

```ts interface CreateArr { (length: number, value: T): Array }

let createArrFn: CreateArr; createArrFn = function (length: number, value: T): Array { let result: T[] = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } `` 上面的接口,使用泛型定義了一個函數。然後創建了createArrFn方法,實現了這個泛型接口的函數。可以傳入兩個參數lengthvalue,其中length為數組的長度所以是number類型,value類型為泛型,根據調用方法時傳入參數的類型決定。最終這個方法會返回一個長度為length`的數組。

泛型類

  • 在類名後面定義泛型T,類裏面使用

ts class GenAny<T> { value: T; add: (x: T, y: T) => T; }

ts const myGen = new GenAny<number>(); myGen.value = 100; myGen.add = (x, y) => x + y; // 調用 myGen.add(3, 4);

  • 應用: 定義一個 Person

ts class Person<T1, T2> { name: T1; age: T2; sex: T1; constructor(name: T1, age: T2, sex: T1){ this.name = name; this.age = age; this.sex = sex; } } 上面的Person類包含了泛型T1 T2name age sex屬性。

實例化:

  1. 自動類型推導,可以不傳入參數的類型:

ts const p1 = new Person("tom", 18, "boy"); 自動推斷出T1string類型,T2number類型。此時如果第三個參數sex傳入非string類型就會提示錯誤,以為已經把T1自動推導為string類型了。

  1. 不用類型推導就要在new實例化的時候傳入類型 ts const p2 = new Person<string, string>("jack", "18", "boy");

  2. 使用類來約束對象並傳遞類型 ts const p3: Person<string, number> = new Person("jack", 18, "boy");

這時候可以發現上一節中用Array類來聲明數組的寫法和使用泛型類約束變量的寫法是很相似的:

ts const arr: Array<number> = [2, 4, 6];