亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

TypeScript 泛型(Generic)

本節開始介紹 TypeScript 一些進階知識點,第一個要介紹的泛型是 TypeScript 中非常重要的一個概念,它是一種用以增強函數、類和接口能力的非常可靠的手段。

使用泛型,我們可以輕松地將那些輸入重復的代碼,構建為可復用的組件,這給予了開發者創造靈活、可重用代碼的能力。

1. 慕課解釋

泛型在傳統的面向對象語言中極為常見,可以使用泛型來創建可重用的組件,一個組件可以支持多種類型的數據。

通俗來講:泛型是指在定義函數、接口或者類時,未指定其參數類型,只有在運行時傳入才能確定。那么此時的參數類型就是一個變量,通常用大寫字母 T 來表示,當然你也可以使用其他字符,如:UK等。

語法:在函數名、接口名或者類名添加后綴 <T>

function generic<T>() {}
interface Generic<T> {}
class Generic<T> {}

2. 初識泛型

之所以使用泛型,是因為它幫助我們為不同類型的輸入,復用相同的代碼。

比如寫一個最簡單的函數,這個函數會返回任何傳入它的值。如果傳入的是 number 類型:

function identity(arg: number): number {
    return arg
}

如果傳入的是 string 類型:

function identity(arg: string): string {
    return arg
}

通過泛型,可以把兩個函數統一起來:

function identity<T>(arg: T): T {
  return arg
}

需要注意的是,泛型函數的返回值類型是根據你的業務需求決定,并非一定要返回泛型類型 T:

function identity<T>(arg: T): string {
  return String(arg)
}

代碼解釋: 入參的類型是未知的,但是通過 String 轉換,返回字符串類型。

3. 多個類型參數

泛型函數可以定義多個類型參數:

function extend<T, U>(first: T, second: U): T & U {
  for(const key in second) {
    (first as T & U)[key] = second[key] as any
  }
  return first as T & U
}

代碼解釋: 這個函數用來合并兩個對象,具體實現暫且不去管它,這里只需要關注泛型多個類型參數的使用方式,其語法為通過逗號分隔 <T, U, K>。

4. 泛型參數默認類型

函數參數可以定義默認值,泛型參數同樣可以定義默認類型:

實例演示
預覽 復制
復制成功!
function min<T = number>(arr:T[]): T{
  let min = arr[0]
  arr.forEach((value)=>{
     if(value < min) {
         min = value
     }
  })
   return min
}
console.log(min([20, 6, 8n])) // 6
運行案例 點擊 "運行案例" 可查看在線運行效果

解釋: 同樣的不用去關注這個最小數函數的具體實現,要知道默認參數語法為 <T = 默認類型>。

5. 泛型類型與泛型接口

先來回顧下之前章節介紹的函數類型:

const add: (x: number, y: number) => string = function(x: number, y: number): string {
  return (x + y).toString()
}

等號左側的 (x: number, y: number) => string 為函數類型。

再看下泛型類型:

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: <T>(arg: T) => T = identity

同樣的等號左側的 <T>(arg: T) => T 即為泛型類型,它還有另一種帶有調用簽名的對象字面量書寫方式:{ <T>(arg: T): T }:

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: { <T>(arg: T): T } = identity

這就引導我們去寫第一個泛型接口了。把上面例子里的對象字面量拿出來作為一個接口:

interface GenericIdentityFn {
  <T>(arg: T): T
}

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: GenericIdentityFn = identity

進一步,把泛型參數當作整個接口的一個參數,我們可以把泛型參數提前到接口名上。這樣我們就能清楚的知道使用的具體是哪個泛型類型:

interface GenericIdentityFn<T> {
  (arg: T): T
}

function identity<T>(arg: T): T {
  return arg
}

let myIdentity: GenericIdentityFn<number> = identity

注意,在使用泛型接口時,需要傳入一個類型參數來指定泛型類型。示例中傳入了 number 類型,這就鎖定了之后代碼里使用的類型。

6. 泛型類

始終要記得,使用泛型是因為可以復用不同類型的代碼。下面用一個最小堆算法舉例說明泛型類的使用:

class MinClass {
  public list: number[] = []
  add(num: number) {
    this.list.push(num)
  }
  min(): number {
    let minNum = this.list[0]
    for (let i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i]
      }
    }
    return minNum
  }
}

代碼解釋: 示例中我們實現了一個查找 number 類型的最小堆類,但我們的最小堆還需要支持字符串類型,此時就需要泛型的幫助了:

實例演示
預覽 復制
復制成功!
// 類名后加上 <T>
class MinClass<T> {
  public list: T[] = []
  add(num: T) {
    this.list.push(num)
  }
  min(): T {
    let minNum = this.list[0]
    for (let i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i]
      }
    }
    return minNum
  }
}


let m = new MinClass<string>()
m.add('hello')
m.add('world')
m.add('generic')
console.log(m.min()) // generic
運行案例 點擊 "運行案例" 可查看在線運行效果

代碼解釋:

第 2 行,在聲明 類 MinClass 的后面后加上了 <T>,這樣就聲明了泛型參數 T,作為一個變量可以是字符串類型,也可以是數字類型。

7. 泛型約束

語法:通過 extends 關鍵字來實現泛型約束。

如果我們很明確傳入的泛型參數是什么類型,或者明確想要操作的某類型的值具有什么屬性,那么就需要對泛型進行約束。通過兩個例子來說明:

interface User {
  username: string
}

function info<T extends User>(user: T): string {
  return 'imooc ' + user.username
}

代碼解釋: 示例中,第 5 行,我們約束了入參 user 必須包含 username 屬性,否則在編譯階段就會報錯。

下面再看另外一個例子:

type Args = number | string

class MinClass<T extends Args> {}

const m = new MinClass<boolean>() // Error, 必須是 number | string 類型

代碼解釋:

第 3 行,約束了泛型參數 T 繼承自類型 Args,而類型 Args 是一個由 number 和 string 組成的聯合類型。

第 5 行,泛型參數只能是 number 和 string 中的一種,傳入 boolean 類型是錯誤的。

8. 多重類型泛型約束

通過 <T extends Interface1 & Interface2> 這種語法來實現多重類型的泛型約束:

interface Sentence {
  title: string,
  content: string
}

interface Music {
  url: string
}

class Classic<T extends Sentence & Music> {
  private prop: T

  constructor(arg: T) {
    this.prop = arg
  }

  info() {
    return {
      url: this.prop.url,
      title: this.prop.title,
      content: this.prop.content
    }
  }
}

代碼解釋:

第 10 行,約束了泛型參數 T 需繼承自交叉類型(后續有單節介紹) Sentence & Music,這樣就能訪問兩個接口類型的參數。

9. 小結

泛型在 TypeScript 中用途廣泛,可以靈活的控制類型之間的約束,提高代碼復用性,增強代碼可讀性。