為vue3學點typescript, 泛型

鐵皮飯盒發表於2019-07-12

直達

第一課, 體驗typescript

第二課, 基礎型別和入門高階型別

第三課, 泛型

第四課, 解讀高階型別

第五課, 名稱空間(namespace)是什麼

特別篇, 在vue3?原始碼中學會typescript? - "is"

第六課, 什麼是宣告檔案(declare)? ? - 全域性宣告篇

插一課

本來打算接著上節課, 把高階型別都講完, 但是寫著寫著我發現高階型別中, 有很多地方都需要泛型的知識, 那麼先插一節泛型.

什麼是"型別變數"和"泛型"

變數的概念我們都知道, 可以表示任意資料, 型別變數也一樣, 可以表示任意型別:

// 在函式名後面用"<>"宣告一個型別變數
function convert<T>(input:T):T{
    return input;
}
複製程式碼

convert中引數我們標記為型別T, 返回值也標記為T, 從而表示了: 函式的輸入和輸出的型別一致. 這樣使用了"型別變數"的函式叫做泛型函式, 那有"泛型類"嗎?

注意: T是我隨便定義的, 就和變數一樣, 名字你可以隨便起, 只是建議都是大寫字母,比如U / RESULT.

泛型類
class Person<U> {
    who: U;
    
    constructor(who: U) {
        this.who = who;
    }

    say(code:U): string {
        return this.who + ' :i am ' + code;
    }
}
複製程式碼

在類名後面通過"<>"宣告一個型別變數U, 類的方法和屬性都可以用這個U, 接下來我們使用下泛型類:

let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 錯誤, 會提示引數應該是個string
a.say('007') // 正確
複製程式碼

我們傳入了型別變數(string),告訴ts這個類的Ustring型別, 通過Person的定義, 我們知道say方法的引數也是string型別, 所以a.say(007)會報錯, 因為007是number. 多以我們可以通過傳入型別變數來約束泛型.

自動推斷型別變數的型別

其實我們也可以不指定型別變數為string, 因為ts可以根據例項化時傳入的引數的型別推斷出Ustring型別:

let a =  new Person('詹姆斯邦德');
// 等價 let a =  new Person<string>('詹姆斯邦德');
a.say(007) // 錯誤, 會提示引數應該是個string
a.say('007') // 正確
複製程式碼
泛型方法

其實方法和函式的定義方式一樣:

class ABC{
    // 輸入T[], 返回T
    getFirst<T>(data:T[]):T{
        return data[0];
    }
}
複製程式碼
泛型是什麼?

說實話ts的文件我找了好幾遍, 也沒看到他給泛型正式做定義, 只是表達他是一種描述多種型別(型別範圍)的格式, 我覺得有點抽象, 我用自己的理解具象下: 用動態的型別(型別變數)描述函式和類的方式.

泛型型別

我們可以用型別變數去描述一個型別(型別範圍), ts的陣列型別Array本身就是一個泛型型別, 他需要傳遞具體的型別才能變的精準:

let arr : Array<number>;
arr = ['123']; // 錯誤, 提示陣列中只可以有number型別
arr = [123];
複製程式碼

下面我們自己定義一個泛型型別, 就對開頭的convert函式定義:

function convert<T>(input:T):T{
    return input;
}

// 定義泛型型別
interface Convert {
    <T>(input:T):T
}

// 驗證下
let convert2:Convert = convert // 正確不報錯
複製程式碼

泛型介面

通過傳入不同的型別引數, 讓屬性更靈活:

interface Goods<T>{
    id:number;
    title: string;
    size: T;
}

let apple:Goods<string> = {id:1,title: '蘋果', size: 'large'};
let shoes:Goods<number> = {id:1,title: '蘋果', size: 43};
複製程式碼

擴充套件型別變數(泛型約束)

function echo<T>(input: T): T {
    console.log(input.name); // 報錯, T上不確定是否由name屬性
    return input;
}
複製程式碼

前面說過T可以代表任意型別, 但對應的都是基礎型別, 所以當我們操作input.name的時候就需要標記input上有name屬性, 這樣就相當於我們縮小了型別變數的範圍, 對泛型進行了約束:

// 現在T是個有name屬性的型別
function echo<T extends {name:string}>(input: T): T {
    console.log(input.name); // 正確
    return input;
}
複製程式碼

一個泛型的應用, 工廠函式

function create<T,U>(O: {new(): T|U; }): T|U {
    return new O();
}
複製程式碼

主要想說3個知識點:

  1. 可以定義多個型別變數.
  2. 型別變數和普通型別用法一直, 也支援聯合型別/交叉型別等型別.
  3. 如果一個資料是可以例項化的, 我們可以用{new(): any}表示.

不要亂用泛型

泛型主要是為了約束, 或者說縮小型別範圍, 如果不能約束功能, 就代表不需要用泛型:

function convert<T>(input:T[]):number{
    return input.length;
}
複製程式碼

這樣用泛型就沒有什麼意義了, 和any型別沒有什麼區別.

總結

泛型是編譯型語言最重要的特性, 泛型寫的好就會讓人覺得程式碼很高階, 可以說泛型是一個成手ts程式設計師必須熟練的技巧, 面試的時候是加分項, 所以大家寫程式碼多多用泛型練習哦, 加油ヾ(◍°∇°◍)ノ゙,下面是的用ts寫的幾個小專案,寫的不好, 就是有份熱情, 拋磚引玉, 大家肯定能寫出更好的:

手勢庫: github.com/any86/any-t…

命令式呼叫vue元件: github.com/any86/vue-c…

工作中常用的一些程式碼片段: github.com/any86/usefu…

一個mini的事件管理器: github.com/any86/any-e…

微信群

感謝大家的閱讀, 如有疑問可以加群?, 群裡有好多有趣的前端的小夥伴, 讓我們共同學習成長吧!

可加我微信, 我拉你進入微信群(由於騰訊對微信群的100人限制, 超過100人後必須由我拉進去)

為vue3學點typescript, 泛型

相關文章