typescript中使用泛型


介紹

這里引入官網一段介紹,了解個大概:

軟件工程中,我們不僅要創建一致的定義良好的API,同時也要考慮可重用性。 組件不僅能夠支持當前的數據類型,同時也能支持未來的數據類型,這在創建大型系統時為你提供了十分靈活的功能。

在像C#和Java這樣的語言中,可以使用泛型來創建可重用的組件,一個組件可以支持多種類型的數據。 這樣用戶就可以以自己的數據類型來使用組件。

 

認識泛型的作用

很多時候我們無法准確定義一個類型,它可以是多種類型,這種情況下我們習慣用 any 來指定它的類型,代表它可以是任意類型。any 雖好用,但是它並不是那么安全的,這時候應該更多考慮泛型。

 

為了理解泛型的作用,舉個例子說明。我們來創建下面這樣的一個函數,傳入什么參數就返回什么參數,這個函數可以看成是一個 echo 命令:

function echoValue(arg: any): any {
  return arg
}

為了不限制傳入的參數類型,所以使用 any 類型。此函數咋一看是沒問題的,但是缺丟失了一些信息,即傳入的類型與返回的類型應該是相同的,使用 any 不能保證這一點。使用 any 不是一個安全的方案,比如我們來改變一下這個函數,返回傳入值的 length :

function echoValue(arg: any): any {
  return arg.length
}

這樣寫不會報任何錯誤,因為 arg 可以是任意值,所以不管做什么操作都是可以的。但如果函數傳入的參數是 number 類型的,顯然它是沒有 length 屬性的,那么執行時程序就會報錯了。例子雖然很牽強,但也能說明問題,any 的不確定性,注定會帶來各種問題,如果動不動就使用 any,那么也失去了使用 typescript 的意義。

 

現在我們使用泛型的方法來改寫上面例子:

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

T 是類型變量,它是一種特殊的變量,只用於表示類型而不是值,使用 <> 定義。定義了類型變量之后,你在函數中任何需要指定類型的地方使用 T 都代表這一種類型,這樣也能保證返回值的類型與傳入參數的類型是相同的了。

 

我們將這個版本的 echoValue 函數稱作“泛型”,因為它適用於多種類型。定義了泛型函數后,有兩種方法調用它,第一種明確指定 T 的類型:

echoValue<string>('hello world')

第二種方法就是直接調用,更普遍。利用了類型推論 -- 即編譯器會根據傳入的參數自動地幫助我們確定 T 的類型:

echoValue('hello world')

 

當定義泛型時,不符合的操作都會報錯,比如返回傳入值的 length 時:

function echoValue<T>(arg: T): T {
  return arg.length  // error,類型“T”上不存在屬性“length”
}

 

使用泛型變量

需要認識到泛型變量 T 可以是整個類型,也可以是某個類型的一部分,比如:

function echoValue<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

定義泛型變量 T,函數參數是各元素為 T 類型的數組類型,返回值是各元素為 T 類型的數組元素。

 

T 並不是固定的,你可以寫成 A、B或者其他名字,而且可以在一個函數中定義多個泛型變量,如下面這個例子:

function getArray<T,U>(arg1: T, arg2: U): [T,U]{
  return [arg1, arg2]
}

我們定義了 T 和 U 兩個泛型變量,第一個參數指定 T 類型,第二個參數指定 U 類型,函數返回一個元組包含類型 T 和 U。

 

泛型類型

我們可以定義一個泛型函數類型,泛型函數的類型與非泛型函數的類型沒什么不同,只是有一個類型參數在最前面。

 

直接定義:

let echoValue: <T>(arg: T) => T = function<T>(arg: T): T {
  return arg
}

 

使用類型別名定義:

type EchoValue = <T>(arg: T) => T
let echoValue: EchoValue = function<T>(arg: T): T {
  return arg
}

 

使用接口定義:

interface EchoValue{
  <T>(arg: T): T
}
let echoValue: EchoValue = function<T>(arg: T): T {
  return arg
}
// 可以使用不同的泛型參數名,只要在數量上和使用方式上能對應上就可以
let echoValue2: EchoValue = function<U>(arg: U): U {
  return arg
}

 

對於接口而言,我們可以把泛型參數當作整個接口的一個參數,這樣我們就能清楚的知道使用的具體是哪個泛型類型。如下:

// 泛型變量作為接口的變量
interface EchoValue<T>{
  (arg: T): T
}
let echoValue: EchoValue<string> = function<T>(arg: T): T {
  return arg
}
echoValue(123) // error,類型“123”的參數不能賦給類型“string”的參數

let echoValue2: EchoValue<number> = function<U>(arg: U): U {
  return arg
}
echoValue2(123)

 

泛型類

泛型類看上去與泛型接口差不多。 泛型類使用( <>)括起泛型類型,跟在類名后面。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

// T 為 number 類型
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

// T 為 string 類型
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

 類有兩部分:靜態部分和實例部分, 泛型類指的是實例部分的類型,所以類的靜態屬性不能使用這個泛型類型。

 

泛型約束

我們有時在操作某值的屬性時,是事先知道它具有此屬性的,但是編譯器不知道,就如上面有個例子,我們訪問 arg.length 是行不通的:

function echoValue<T>(arg: T): T {
  console.log(arg.length) // 類型“T”上不存在屬性“length”
  return arg
}

 

現在我們可以通過泛型約束來對泛型變量進行約束,讓它至少包含 length 這一屬性,具體實現如下:

// 定義接口,接口規定必須有 length 這一屬性
interface Lengthwise{
  length: number
}

// 使用接口和 extends 關鍵字實現約束,此時 T 類型就必須包含 length 這一屬性
function echoValue<T extends Lengthwise>(arg: T): T {
  console.log(arg.length) // 通過,因為被約束的 T 類型是包含 length 屬性的
  return arg
}

現在這個泛型函數被定義了約束,因此它不再是適用於任意類型:

echoValue(3) // 類型“3”的參數不能賦給類型“Lengthwise”的參數

echoValue({value: 3, length:10}) // right

echoValue([1, 2, 3]) // right

 

泛型約束中使用類型參數

當我們定義一個對象,想對它做一個要求,即只能訪問對象上存在的屬性,該怎么做?來看看這個需求的樣子:

const getProps = (obj, propName) => {
  return obj[propName]
}

const o = {a: 'aa', b: 'bb'}

getProps(o, 'c') // undefined
我們都知道當訪問這個對象的’c’屬性時,這個屬性是沒有的,但是在開發時是不會提醒報錯的。在 typescript 中,我們可以實現對這個問題的檢查,要使用到一個 keyof 關鍵字:
const getProps = <T, K extends keyof T>(obj: T, propName: K) => {
  return obj[propName]
}

const o = {a: 'aa', b: 'bb'}

getProps(o, 'c') // error,類型“"c"”的參數不能賦給類型“"a" | "b"”的參數
這里我們使用讓 K 來繼承索引類型 keyof T,可以理解 keyof T 相當於一個由泛型變量 T 的屬性名構成的聯合類型,這里的 K 就被約束為了只能是 'a' 或 'b',所以當我們傳入字符串 'c' 想要獲取對應屬性時就會報錯。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM