TypeScript真香系列-泛型

前言

TypeScript真香系列的內容將參考中文文檔,但是文中的例子基本不會和文檔中的例子重複,對於一些地方也會深入研究。另外,文中一些例子的結果都是在代碼沒有錯誤後編譯爲JavaScript得到的。如果想實際看看TypeScript編譯爲JavaScript的代碼,可以訪問TypeScript的在線編譯地址,動手操作,印象更加深刻。

概念

TypeScript的泛型與其他面向對象的語言中的定義是相似的。泛型可以理解爲在我們定義函數、接口或者類的時候,不預先指定其相關的類型,而是在使用的時候手動指定類型。這和TypeScript基礎類型中的any是有區別的。

當我們不使用泛型的時候,可能就如下這樣:

function show(name: string): string {
    return name;
}

function show(name: any): any {
    return name;
}

上面例子中用any或許沒有什麼問題,但是當我們想傳入的參數類型和返回值類型相同時,使用any時的返回值就可能有多種情況了。還有就是我們平時也會考慮重用性,提取公共組件等。這時泛型就能派上了。下面是一個泛型函數的例子:

function show<T>(arg: T): T {
    return arg;
}

這個show函數後面的 T ,叫做類型變量,這個我們後面會介紹。這個函數我們可以用兩種方法調用。
第一種爲傳入參數,包括類型參數:

show<string>("james"); //"james"

第二種爲不包括類型參數,這種方式利用了類型推論,這個再後面的章節會講到:

show(1); //1

泛型變量

上面所寫的例子中,我們可以在“<>”中指定一個變量T,並且我們要把這個T定義在參數和返回值上。編譯器在檢查我們的代碼的時候,我們必須在函數體中正確的使用這個通用的類型。

function show<T>(arg: T): T {
    console.log(arg.length)  //錯誤,類型T不存在length屬性
    return arg;
}

上面的例子中,我們在打印arg的length中報錯了。原因是我們在使用這個函數的時候,傳入的參數可能爲數字,而數字是沒有length屬性的。
當然,我們在調用函數的時候也要正確調用:

function show<T>(arg: T): T {
    return arg;
}
show<string>(1); //錯誤,參數1的類型不是string
show<>(1); //錯誤,參數類型不能爲空

針對數組:

function show<T>(arg: T[]): T[] {
    return arg;
}
show<string>(["奧","利","給"]);  //["奧", "利", "給"]

function show<T>(arg: Array<T>): Array<T> {
    return arg;
}
show<number>([6, 6, 6]); // [6, 6, 6]

泛型接口

如下所示:

interface IMan { 
    <T>(arg: T): T
}

function man<T>(arg: T): T{
    return arg;
}

let showMan: IMan = man;
showMan("james"); //"james"

還有另外一種寫法,這種方法我們可以把泛型參數當做整個接口的參數:

interface IMan<T> { 
    (arg: T): T
}

function man<T>(arg: T): T{
    return arg;
}

let showMan: IMan<string> = man;
showMan("james"); //"james"

這樣的好處就是在使用時知道具體的參數類型了。

泛型類

我們可以使用(<>)和尖括號中間的變量,然後跟在類名的後面來表示泛型類。需要注意的是,泛型類指的是實例部分的類型,類的靜態屬性不能使用泛型類型。

class Man<T> { 
    name: T;

    constructor(arg: T) {
        this.name = arg;
    }

    play(a: T) : T {
        return a;
    }
    
}
let showMan = new Man<string>("James");
showMan.name;  //"James"
showMan.play("ball"); //"ball"

也可以稍微複雜一點:

class Man<T> { 
    name: T;
    sum: T[];
    
    constructor(arg: T) {
        this.name = arg;
        this.sum = [];
        
    }

    push(item: T): void{
        this.sum.push(item) 
    }
    output() { 
         return this.sum;
    }
}
let showMan = new Man<string>("James");
showMan.push("one for all");
showMan.output(); //["one for all"]

泛型約束

讓我們回到泛型變量一節中的一個例子:

function show<T>(arg: T): T {
    console.log(arg.length)  //錯誤,類型T不存在length屬性
    return arg;
}

如果我們想要使用length屬性,那該怎麼辦?可以看下面的例子:

interface ILen {
    length: number;
 }

function show<T extends ILen>(arg: T): T {
    console.log(arg.length)  //沒有報錯了
    return arg;
}

雖然上面的例子沒有再報錯,但是因爲這個泛型函數被定義了約束,所以我們再調用函數的時候又有了講究,也就是我們在傳值的時候需要像下面的例子一樣:

interface ILen {
    length: number;
 }

function show<T extends ILen>(arg: T): T {
    console.log(arg.length)  
    return arg;
}
show(3); //錯誤,參數3不能分配給類型ILen 
show({ length: 2 }); // 2  {length: 2}
show({length:2, arg:1}) //2  {length: 2, arg: 1}
show({length:2, arg:"james"}) //2 {length: 2, arg: "james"}

參考

https://github.com/zhongsp/TypeScript

最後

文中有些地方可能會加入一些自己的理解,若有不準確或錯誤的地方,歡迎指出~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章