【前端】你好,我叫TypeScript 03──數據類型
泛型
寫在前面
今天是520,祝大家有情人終成眷屬吧,咱們也應應景,解鎖兩個新英雄:蹦蹦和跳跳,探索以全新的問答視角來介紹和解決亟需處理的問題。
什么是泛型?
「蹦蹦」:跳跳,你曉得啥子是泛型嘛?
「跳跳」:在歷史中,強者高手都是考慮現在和未來的,只有宏觀把握才能讓自己立于不敗之地。就像你耍游戲撒,不管你要出啥子大件裝備,你先買一雙孩子或者多蘭劍、藍色戒指你就不會錯撒,反正都是要在這個方面拓展。
而在面向對象的編程語言中,組件不僅要考慮到現在的數據類型,還要考慮到未來要使用的數據類型,而「泛型」完美的提供了組件的「可復用性」,提高組件的靈活性。曉得咯不?
為什么要設計泛型?
「蹦蹦」:跳跳,那么為什么要設計泛型撒?
「跳跳」:設計泛型的目的就是在成員之間提供有意義的約束,而這些成員包括:類的實例成員、類的方法、函數參數和函數返回值。
「跳跳」:JS老哥他是作為一門動態語言存在的,存在很大的靈活性,只有在執行過程中變量賦值了,你才曉得這個變量是啥子類型的。那么這就存在一個隱患,我不曉得要事先賦值啥子類型的變量,那么在執行過程中就會存在類型不對的錯誤,這就降低了代碼的可維護性。而TS中有三種方法可以解決JS的可復用性差、可維護性差等問題,那么我們來看看是哪些:
函數重載
- function getVal(val: number): number
- function getVal(val: string):string
- function getVal(val: any):any {
- return val;
- }
聯合類型
- function getVal(val: string | number | any[]):string | number | any[] {
- return val;
- }
在追求代碼的簡潔可讀的時代,你寫這又臭又長的代碼是幾個意思,有點瓜兮兮。而TS小老弟還是會來事,提前考慮到了這些問題,提供泛型方式來解決這些問題。
泛型
- function getVal<T>(val: T): T {
- return val;
- }
解釋哈泛型的規則,上面的「T表示的是待捕獲函數傳入參數類型(Type),在函數內部使用T可用于該參數類型聲明其他變量」。實際上T并不是固定的,可以用任何有效名稱替代。比如:
- K(Key):表示對象中的鍵類型
- V(Value):表示對象中的值類型
- E(Element):表示元素的類型
如何使用泛型?「蹦蹦」:那么愣個使用泛型撒?
「跳跳」:愣個使用泛型,系好安全帶,我們要出發了。
我們看到,當我們調⽤ identity
「蹦蹦」:講的還是挺詳細的哈,那么那個<>里面可以寫兩個變量類型不?
「跳跳」:這個肯定闊以撒,哪怕你兩個,兩百個都闊以。我就舉個栗子撒,我們闊以看到下面的代碼中,用了兩個類型變量⽤于擴展我們定義的 identity 函數:
除了為類型變量顯式設定值之外,⼀種更常⻅的做法是使編譯器⾃動選擇這些類型,從⽽使代碼更簡潔。我們可以完全省略尖括號,利用了類型推論──即編譯器會根據傳入的參數自動地幫助我們確定T的類,類型推論幫助我們保持代碼精簡和高可讀性。
- let output = identity<string>("myString"); // type of output will be 'string'
- let output = identity("myString"); // type of output will be 'string'
泛型接口和泛型類
「蹦蹦」:跳跳,我昨天看到「一川」寫的關于接口的文章,看到有對象接口、類接口啥的,泛型是不是也有相關的概念。
「跳跳」:不錯哈,都會進行搶答了。我們先看看泛型接口:
- interface FanxingInter<T>{
- //定義了一個非泛型函數簽名作為泛型類型的一部分
- (name:T):T;
- }
- function func<T>(name:T):T{
- return name;
- }
- // 在使用泛型接口時,需要傳入一個類型參數來指定泛型類型(這里是number),鎖定了之后代碼里使用的類型
- let fxFun: FanxingInter<string> = func;
- fxFun("yichuan");
我們再看看,泛型如何在類中進行使用,定義泛型類的。
其實,泛型類看上去與泛型接口差不多。泛型類使用(<>)括起泛型類型,跟在類名后面,用于定義任意多個類型變量。
- interface FxInterface<T>{
- value:T;
- getValue:()=>T;
- }
- class FxClass<T> implements FxInterface<T>{
- value:T;
- constructor(value:T){
- this.value = value;
- }
- getValue():T{
- return this.value;
- }
- }
- const myFxClass = new FxClass<string>("yichuan");
- console.log(myFxClass.getValue());
調用過程:
- 先實例化對象FxClass,傳入string類型的參數值"yichuan";
- 在FxClass類中,類型變量T的值變成string類型;
- FxClass類實現了FxInterface
,此時T表示的是string類型,即實現了泛型接口;
我們歸納一下,就是:
- function Func<T>(){} //泛型函數,尖括號跟在函數名后
- class Dog<T>{} //泛型類,尖括號跟在類名后
- interface NiuInterface<T>{} //泛型接口,尖括號跟隨接口名后
「跳跳」:蹦蹦,泛型接口和泛型類,懂了不。
泛型約束
「蹦蹦」:懂了。我突然想到一個問題,如果想要去操作某類型對應的某些屬性,比如說訪問參數name的length,編譯器為什么會報錯?
- function nameFunc<T>(name:T):T{
- console.log(name.length);//Error
- return name;
- }
「跳跳」:這個問題挺不錯了,說明你對泛型整挺好哈。我們看到,因為編譯器也不知道你輸入的參數類型T是否具有length屬性,要解決它可以讓那個類型變量extends含有所需屬性的接口。
- interface Length{
- length: number;
- }
- function idenLength<T extends Length>(name: T):T{
- console.log(name.length);
- return name;
- }
T extends Length是對泛型的約束,告訴編譯器已經支持Length接口的任何類型。對于使用不含length屬性的對象作為參數調用函數時,ts會提示相應的錯誤信息:
- console.log(idenLength(18));//Error
此外,我們還可以使⽤ , 號來分隔多種約束類型,⽐如:
高端玩家──索引類型
「蹦蹦」:泛型中如何檢查對象的鍵是否存在呢?
「跳跳」:其實也很簡單,同樣使用泛型約束進行檢測。只不過需要通過索引類型查詢操作符:keyof,用于獲取某種類型T所有的鍵,返回類型的公共屬性的聯合類型。
- interface Person{
- name:string;
- age:number;
- }
- let personProps: keyof Person;//"name" | "age"
我們就可以結合前⾯介紹的 extends 約束,即限制輸⼊的屬性名包含在 keyof 返回的聯合類型中。
- function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
- return obj[key];
- }
在以上的 getProperty 函數中,我們通過 K extends keyof T確保參數 key⼀定是對象中含有的鍵,這樣就不會發⽣運⾏時錯誤。
- class BeeKeeper {
- hasMask: boolean;
- }
- class ZooKeeper {
- nametag: string;
- }
- class Animal {
- numLegs: number;
- }
- class Bee extends Animal {
- keeper: BeeKeeper;
- }
- class Lion extends Animal {
- keeper: ZooKeeper;
- }
- function createInstance<A extends Animal>(c: new () => A): A {
- return new c();
- }
- createInstance(Lion).keeper.nametag; // typechecks!
- createInstance(Bee).keeper.hasMask; // typechecks!
「蹦蹦」:懂了,懂了,就是有點復雜,得去捋一捋。
小結
「跳跳」:其實本篇文章主要介紹了:泛型的概念、泛型接口和泛型類、泛型約束以及索引類型等等。
參考文章
- 阿寶哥的《重學TS》
- 《ts中文文檔》
本文轉載自微信公眾號「前端萬有引力」,可以通過以下二維碼關注。轉載本文請聯系前端萬有引力眾號。