學 Java 的時候總會提到泛型,現在 TS 也有了,他們的用法都差不太多。泛型可以理解為廣泛的類型。
為什么要用泛型
先來了解下泛型有什么用。先看下面定義的一個函數:
function itself(a: number): number { return a } itself(1) // 1
上面的函數就是簡單的傳入一個數,返回一個數,但是現在想傳入字符串,返回字符串呢?或者再加傳入數組,返回數組呢?這就需要用到泛型了。
function itself<T>(a: T):T { return a } console.log(itself(true)) // true console.log(itself(1)) // 1 console.log(itself('1')) // '1' console.log(itself([1, 2, 3])) // [1, 2, 3]
從上面的例子可以看到,T 有點像是一個臨時變量一樣,先推斷出傳入參數的類型,將這個類型賦值到 T T = XXX 類型
,后面就直接 返回的類型 = T = XXX 類型
。這么說可能不那么准確,但是可以看到 T 就是一個占位符。
如果調用函數時使用顯式聲明可能會更直觀。
function itself<T>(a: T):T { return a } console.log(itself<boolean>(true)) // true console.log(itself<number>(1)) // 1 console.log(itself<string>('1')) // '1' console.log(itself<Array<number>>([1, 2, 3])) // [1, 2, 3
這里發現 Array<number>
里也是用了泛型的,這樣可以聲明數組里元素的類型。
接口與泛型
上面都是傳基本類型(除了 Array),下面看看接口與泛型的配合。
interface Human { name: string age: number } interface Animal { category: string } function create<T>(what: T): T { return what } create<Human>({ name: 'Jack', age: 18 }) create<Animal>({ category: 'dog' })
用法幾乎一樣,Easy~。
有了接口這個東西后,我們可以玩更高級一點的。現在我想造一個 Human,規定這個 Human 一定要有 JJ,可以這么寫。
interface JJ { jjSize: string jjLength: number } interface Human { name: string age: number } function create<T extends JJ>(what: T): T { return what } create({ name: 'Jack', age: 18, jjSize: 'large', jjLength: 18 }) create({ name: 'Jack', age: 18 }) // 報錯:沒有 jjSize 和 jjLength
上面的這個用法叫做泛型的約束,像剛剛說的這個 Human 一定要有 JJ,這就是一個約束。
類與泛型
說完接口,肯定逃不了要說類了。我們先來看一段 JS 的代碼。
function create(C) { return new C() }
正確的理解應該是傳入一個構造函數 C,然后返回 C 的實例,沒了。但是這個函數啥約束都沒有,如果傳入一個字符串 "Hello" 呢?那不是炸了?所以我們要用 TS 里的泛型去約束它。
function create<T>(C: { new () }) { return new C() }
兩個括號里寫的是 new ()
說明傳入的 C 是可以用 new C()
的。然后我們再加個上返回類型和傳入的類型,可以寫成這樣。
function create<T>(c: { new (): T }): T { return new c() }