泛型,字面上看就是寬泛的類型約束。是指在定義函數、接口或類的時,不指定類型,在使用時指定類型(runtime)。git
考慮到重用性和擴展性,由於組件除了支持當前數據類型,還要考慮將來須要支持新的類型,這是一個合格程序員應有的素養和一個好系統的衡量標準之一(功能的靈活性)。程序員
設計一個函數:輸入什麼類型,輸出也是什麼類型github
// generics.ts function same(arg: number): number { return arg; } console.log(same(18)); // 18 複製代碼
這個只適合數字類型,換成字符串就得再寫一個,無重複性可言,那就得改造typescript
// generics2.ts function same2(arg: any): any { return '十八'; } console.log(same2(18)); // 十八 複製代碼
沒報錯,可是違背了輸入類型和輸出類型相同這個要求。數組
重點來了,咱們須要markdown
// generics3.ts function same3<T>(arg: T): T { return '十八'; } console.log(same3(18)); function same3_1<T>(arg: T): T { return arg; } console.log(same3_1<number>(18)); console.log(same3_1('十八')); // 0.1.2/generics3.ts:2:5 - error TS2322: Type '"十八"' is not assignable to type 'T'. // '"十八"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'. // 2 return '十八'; 複製代碼
剖析下函數
same3
後面添加 <T>
(可理解爲定義類型變量,你也能夠叫 S
);T
自己理解爲類型變量,輸入類型(18
和 十八
)時就是賦值這個類型變量是什麼類型(對應 number
和 string
);T
來約束輸出類型;same3_1<number>(18)
;same3_1('十八')
,這是利用類型推論(編譯器根據輸入類型自動幫助捕獲 T
的類型),很顯然這個使用簡單,推薦使用;same3_1
能夠適合任何類型,管它叫泛型也是實至名歸,恭喜你 same3_1
;有時,參數也不止一個,多個狀況下oop
// generics4.ts function info<S, N>(name: S, age: N): [S, N] { return [name, age]; } console.log(info('pr', 18)); // [ 'pr', 18 ] 複製代碼
// generics4_1.ts const generics4_1 = <T>(arg: T): T => { return arg; } let generics4_1_1: <S>(arg: S) => S = generics4_1; console.log(generics4_1(18)); // 18 console.log(generics4_1_1('十八')); // 十八 複製代碼
<T>、<S>
;T、S
,只要數量上和使用方式上對應上就行了;// generics4_2.ts interface Generics4_2 { <S>(arg: S): S; } const generics4_2 = <T>(arg: T): T => { return arg; } let generics4_2_1: <T>(arg: T) => T = generics4_2; let generics4_2_2: { <S>(arg: S): S } = generics4_2; // 對象字面量 let generics4_2_3: Generics4_2 = generics4_2; // 泛型接口 console.log(generics4_2(18)); // 18 console.log(generics4_2_2('十八')); // 十八 console.log(generics4_2_2('pr')); // pr 複製代碼
{ <S>(arg: S): S }
來定義泛型函數;Generics4_2
來定義泛型函數;其實和泛型接口很像,一塊兒看看,順便對比下post
// genericsClass.ts class Sum <T>{ zero: T; add: (x: T, y: T) => T } const sum = new Sum<number>(); sum.zero = 0; sum.add = (x, y) => (x + y); console.log(sum.add(5, 6)); // 11 const sum1 = new Sum<string>(); sum1.zero = '0'; sum1.add = (x, y) => (x + y); console.log(sum1.add('5', '6')); // 56 複製代碼
sum
和 sum1
你會發現不單單限爲 number
,string
也能夠;上面例子的函數體過於簡單,對於參數的屬性或方法並無使用。因爲咱們事先並不知道輸入參數是什麼類型,不能貿然使用參數的屬性,好比輸入參數是數字,咱們知道數字沒有 length
屬性,看看 Typescript 給咱們報什麼錯(有點找茬的意思哈)。ui
// generics5.ts function same5<T>(arg: T): T { console.log(arg.length); return arg; } console.log(same5(18)); console.log(same5('十八')); // 0.1.2/generics5.ts:2:21 - error TS2339: Property 'length' does not exist on type 'T'. // 2 console.log(arg.length); 複製代碼
如咱們推測的同樣,泛型 T
在不知道什麼類型的狀況下不知道是否包含屬性 length
,因此編譯報錯了。
那如何解決呢?萬能的接口能夠幫助咱們
// generics6.ts interface Length { length: number; } function same6<T extends Length>(arg: T): T { console.log(arg.length); return arg; } console.log(same6(18)); console.log(same6('十八')); console.log(same6(['pr', '30', 'boy'])); // 0.1.2/generics6.ts:9:19 - error TS2345: Argument of type '18' is not assignable to parameter of type 'Length'. // 9 console.log(same6(18)); 複製代碼
解釋下
extends
繼承接口 Length
來約束泛型 T
符合接口屬性,說人話就是輸入的類型必須包含 length
屬性了十八
和數組 ['pr', '30', 'boy']
符合要求,數字 18
明顯不符合要求了;那多個參數的泛型約束怎麼處理?
// generics7.ts function info2<S extends N, N>(name: S, age: N): [S, N] { return [name, age]; } console.log(info2('pr', 18)); console.log(info2(30, 18)); // 0.1.2/generics7.ts:5:19 - error TS2345: Argument of type '"pr"' is not assignable to parameter of type 'number'. // 5 console.log(info2('pr', 18)); 複製代碼
報錯緣由在於,參數 pr
不能分配給類型 number
的參數。