TS中的泛型和裝飾器

本文目錄:

  • 1.什麼是泛型
  • 2.使用泛型變量
  • 3.泛型接口
  • 4.泛型類
  • 5.泛型約束
  • 6.裝飾器

1.什麼是泛型

泛型的定義:
在定義函數、接口或者類的時候,不預先指定具體的類型,而是在使用的時候再指定類型的一種特性。
泛型的優點:
提高代碼可重用性,使用泛型來創建可重用的組件,一個組件可以支持多種類型的數據。
我們有一個需求, 要定義一個函數,這個函數返回的值類型和傳入的參數的類型一致

function f(arg: number): number {
     return arg;
}

但是這個函數只能傳入number類型,如果要傳入字符串,必須要改函數,或者換成any類型也可以達到效果,但是就失去了一些重要的類型信息,體會不到ts帶來的好處。 這個時候我們使用泛型,就可以很好的實現這個需求

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

T 是類型變量(也可以叫類型參數),它是一種特殊的變量,只用於表示類型而不是值; 可以是任意字符 例如 U M都可以。幫助我們捕獲用戶傳入的類型

泛型函數的兩種使用方式:
第一種方式,編譯器能夠自動地推斷出類型,這種方式更普遍一些

let v1 = f(123)
let v2 = f('字符串')

鼠標懸停上去,就可以分別得到 返回類型 number 和string

第二種方式,傳入所有的參數,包含類型參數

let v3 = f<boolean>(false)
let v4 = f<string>('hello')

我們把這個函數f叫做泛型,因爲它可以適用於多個類型;不同於使用ant,它不會丟失信息

2.使用泛型變量

將類型變量(也可以叫類型參數,泛型變量)當做一個類型使用

function fn1<U>(argc: U[]): U[] {
    console.log(argc.length);
    return argc;
}
fn1([1, 2, 3, 4, 4]);

在上面的代碼中,泛型函數定義了一個類型變量T, 將這個類型變量當做我們類型類使用; 我們接收的參數是T類型的數組,返回的也是T類型的數組,這個T可以是任意類型;增加了程序的靈活性

使用多個泛型變量
泛型變量T使我們常用的一個字符,可以是任意字符,可以是多個字符
我們現在要寫一個函數,交換任意兩個類型組成的元組類型

function swap<T, U>(param: [T, U]): [T, U] {
    return [param[0], param[1]];
}
swap([1, 'a']);
swap([[1, 2, 3], { name: 123 }]);

3.泛型接口

就是將泛型的類型變量和我們的接口結合起來,讓接口可以支持多種類型,更加靈活
我們使用之前學習過的函數表達式的方式創建一個函數

let fn3 = function(x: string, y: string): string[] {
    return [x, y];
};

這個fn3函數的類型我們沒有定義,是利用的 類型推論自動獲取的,現在使用接口來定義一個符合我們這個函數需要的形狀

interface MyFn {
    (x: string, y:string): string[]
}
// 這個時候就可以聲明一個帶類型的函數
let fn3:MyFn;

這個類型再修改一下,增加接口的複用性,將參數string換成動態的,由使用者決定;那麼我們就需要使用泛型

interface MyFn {
    <T>(x: T, y: T):T[]
}
let fn3:MyFn;

到這裏我們的這個函數接口形狀就已經完成,還可以將泛型參數提升到我們的接口名稱上

interface MyFn<T> {
    (x: T, y:T): T[]
}
let fn3:MyFn;

4.泛型類

泛型類看上去與泛型接口差不多。 泛型類使用( <>)括起泛型類型,跟在類名後面。用於類的類型定義

類有兩部分:靜態部分和實例部分。 泛型類指的是實例部分的類型,所以類的靜態屬性不能使用這個泛型類型。
與接口一樣,直接把泛型類型放在類後面,可以幫助我們確認類的所有屬性都在使用相同的類型class GenerNum<T>

class GenerNum<T> {
    zero: T;
    add: (x: number, y: T) => T;
}

和接口一樣,在使用這個類的的時候,還得傳入一個類型參數來指定泛型類型

let myGeNum = new GenerNum<string>();
myGeNum.zero = '0';
myGeNum.add = function(x, y) {
    return x.toString() + y;
};

5.泛型約束

在函數內部使用泛型變量的時候,由於事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法
語法: 使用 泛型變量T extends 繼承 我們定義的接口; 約束了必須符合的形狀
我們通過一個代碼來解釋什麼是類型約束

function f3<U>(arg: U): U {
    console.log(arg.length);
    return arg;
}

上面的代碼中,泛型 T 不一定包含屬性 length,所以編譯的時候報錯了
我們可以對泛型進行約束,只允許這個函數傳入那些包含 length 屬性的變量。這就是泛型約束

interface LengthIn {
    length: number;
}
function f3<U extends LengthIn>(arg: U): U {
    console.log(arg.length);
    return arg;
}

如果輸入數字123,直接編譯報錯:類型“123”的參數不能賦給類型“LengthIn”的參數

// console.log(f3(123));
console.log(f3('hello'));
console.log(f3([1, 2, 3]));
console.log(f3({length:12, value: '測試'}));

6.裝飾器

隨着TypeScript和ES6裏引入了類,在一些場景下我們需要額外的特性來支持標註或修改類及其成員。 裝飾器(Decorators)爲我們在類的聲明及成員上通過元編程語法添加標註提供了一種方式

若要啓用實驗性的裝飾器特性,你必須在命令行或tsconfig.json裏啓用experimentalDecorators編譯器選項

裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數上。它可以在不修改代碼自身的前提下,給已有代碼增加額外的行爲

裝飾器使用 @expression這種形式,expression求值後必須爲一個函數,它會在運行時被調用,被裝飾的聲明信息做爲參數傳入

有 5 種裝飾器:類裝飾器、屬性裝飾器、方法裝飾器、訪問器裝飾器、參數裝飾器;我們這裏只簡單的介紹一下前兩種(這也是在angular裏面大量使用的兩種) 類裝飾器、屬性裝飾器

裝飾器寫法: 普通裝飾器(無法傳參), 裝飾器工廠(可以傳參),一般都是這種方式; 給修飾器加上參數,或者叫做'註解',或者叫元數據 (元數據編程)

我們先來看第一種普通裝飾器
1.類裝飾器
沒有參數的裝飾器,類裝飾器的函數的參數就是 當前類的構造函數本身

function Component(param) {
   console.log('這是裝飾器');
   console.log(param);
}

我們創建一個SideBar的側邊欄的組件類, 用組件裝飾器修飾這個類

@Component
class SideBar {}

裝飾器對類的行爲的改變,是代碼編譯時發生的(不是TypeScript編譯,而是js在執行機中編譯階段),而不是在運行時。這意味着,修飾器能在編譯階段運行代碼。也就是說,裝飾器本質就是編譯時執行的函數。
類裝飾器裏面就只有一個參數, 值就是被裝飾的類的構造函數

利用函數柯里化解決傳參問題, 向裝飾器傳入一些參數,也可以叫 參數註解

function Component(param) {
  return function(target) {
    console.log('這是可以傳參的裝飾器');
    // 這個param就是裝飾器的元數據,外界傳遞進來的參數
    console.log(param);
    console.log(target);
  };
}

在使用裝飾器的時候
這個裝飾器裝飾緊跟在後面的類並增加一些屬性,同時爲其指定元數據

@Component({
  templateUrl: 'aaaa',
  styleUrl: 'bbb'
})
class SideBar {}

屬性裝飾器
屬性裝飾器表達式會在運行時當做函數被調用,有兩個參數
第一個參數: 對於靜態成員來說是 構造函數; 對於實例成員來說是原型對象; 第二個參數: 當前屬性的名稱

function Input(param?: any) {
  return function(target, attr) {
    // 屬性裝飾器裏面的target 就是類的原型對象, 和類裝飾器的第一個函數不一樣
    // 就是說在  類裝飾器裏面 target就是構造函數A,屬性裝飾器裏面 target就是A.prototype
    console.log(target, attr);
    // 第二個參數attr就是 當前屬性名; 我們可以設置爲可選參數
    console.log(param);
  };
}
class SideBar {
  @Input() public list1: any;
  @Input('self-defined') public list: any;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章