三趾代、TypeScript 泛型

什么是泛型

理解:泛型就是在編譯期間不確定類型(廣泛之意思),在調(diào)用時(shí)丰辣,由程序員指定的泛型具體指向什么類型撒强。泛型在傳統(tǒng)面向?qū)ο缶幊陶Z言中是極為常見的,ts中當(dāng)然也執(zhí)行泛型笙什。

通俗理解:泛型就是解決 類 接口 方法的重用性飘哨、以及對(duì)不特定數(shù)據(jù)類型的支持

泛型的好處

1. 函數(shù)和類可以輕松的支持多種類型,增強(qiáng)程序的擴(kuò)展性

2. 不必寫多條函數(shù)重載琐凭,冗長(zhǎng)的聯(lián)合類型聲明芽隆,增強(qiáng)代碼的可讀性

3. 靈活控制類型之間的約束


泛型初識(shí)
需求:有個(gè)函數(shù)會(huì)返回任何傳入它的值。

不用泛型的話淘正,這個(gè)函數(shù)可能是下面這樣:

function getData(value: string): string{
  return value;//傳入什么返回什么
}

但是這樣摆马,這個(gè)函數(shù)我只能傳字符串類型的參數(shù)給這個(gè)函數(shù),如果想傳數(shù)字類型鸿吆,布爾類型呢囤采。這個(gè)時(shí)候可以使用 any來實(shí)現(xiàn)

function getData(value: any): any{
  return value;//傳入什么返回什么
}

any 的缺點(diǎn):\color{red}{一個(gè)變量設(shè)置類型為any后,相當(dāng)于該變量關(guān)閉了TS的類型檢測(cè)惩淳,一般情況下不建議使用any}

如果想做到傳入什么類型就返回什么類型蕉毯,例如傳入number就返回number,這時(shí)候就可以使用泛型

function getData<T>(value:T):T{
  return value;//傳入什么返回什么
}
第一種是思犁,傳入所有的參數(shù)代虾,包含類型參數(shù):
getData<number>(123);
getData<string>("123");

第二種方法更普遍。利用了類型推論 – 即編譯器會(huì)根據(jù)傳入的參數(shù)自動(dòng)地幫助我們確定T的類型:
getData(123);
getData("123");

泛型類型

一激蹲、泛型函數(shù)

泛型函數(shù)的類型與非泛型函數(shù)的類型沒什么不同棉磨,只是有一個(gè)類型參數(shù)在最前面,像函數(shù)聲明一樣:

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

let myIdentity: <T>(arg: T) => T = identity;

我們也可以使用不同的泛型參數(shù)名学辱,只要在數(shù)量上和使用方式上能對(duì)應(yīng)上就可以乘瓤。

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

let myIdentity: <U>(arg: U) => U = identity;

我們還可以使用帶有調(diào)用簽名的對(duì)象字面量來定義泛型函數(shù):

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

對(duì)象字面量的方式來定義泛型類型;
let myIdentity: {<T>(arg: T): T} = identity;
二环形、泛型接口

這引導(dǎo)我們?nèi)懙谝粋€(gè)泛型接口了。 我們把上面例子里的對(duì)象字面量拿出來做為一個(gè)接口:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

1衙傀、函數(shù)泛型的注解方式;
let myIdentity: <U>(arg: U) => U = identity;
2抬吟、對(duì)象字面量的方式來定義泛型類型;
let myIdentity: {<T>(arg: T): T} = identity;
3、接口泛型的注解方式;
let myIdentity: GenericIdentityFn = identity;

想把泛型參數(shù)當(dāng)作整個(gè)接口的一個(gè)參數(shù)统抬。
這樣我們就能清楚的知道使用的具體是哪個(gè)泛型類型

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity;

例子:

interface GenericIdentityFn{
  (value1:string, value2:string):string;
}

function identity(value1:string, value2:string):string{
  return value1 + value2;
}

let myIdentity: GenericIdentityFn = identity
myIdentity("name", "張三")

泛型實(shí)現(xiàn)上面函數(shù)
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;
三火本、泛型類

泛型類看上去與泛型接口差不多。 泛型類使用( <> )括起泛型類型聪建,跟在類名后面钙畔。

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

使用 number 類型
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.add(1, 2))

也可以使用字符串或其它更復(fù)雜的類型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "第一次    ";
stringNumeric.add = function (x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "testW逼H婿盅弛!"));

示例:

普通類

class MinClass {
  public list: number[] = [];
  add(num: number) {
    this.list.push(num);
  }
  min(): number {
    var minNum = this.list[0];
    for (var i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i];
      }
    }
    console.log(this.list);
    return minNum;
  }
}
// 調(diào)用
var m = new MinClass();
m.add(3);
m.add(2);
m.add(4);
console.log(m.min());// 2

上邊的只能傳入數(shù)字類型钱骂,使用泛型實(shí)現(xiàn)上面的 類

class MinClass<T>{
  public list: T[] = [];
  add(num: T): void {
    this.list.push(num);
  }
  min(): T {
    var minNum = this.list[0];
    for (var i = 0; i < this.list.length; i++) {
      if (minNum > this.list[i]) {
        minNum = this.list[i];
      }
    }
    return minNum;
  }
}
// 調(diào)用,實(shí)例化時(shí)候要先聲明參數(shù)類型<bumber
var m = new MinClass<number>();
m.add(2);
m.add(4);
m.add(3);
console.log(m.min());// 2
四挪鹏、約束泛型

作某類型的一組值见秽,并且我們知道這組值具有什么樣的屬性。這個(gè)時(shí)候我們想訪問arg的length屬性時(shí)讨盒,例如:

function loggingIdentity<T>(arg: T): T {
  console.log(arg.length)  // Property 'length' does not exist on type 'T'.
  return arg
}

想訪問arg的length屬性解取,但是編譯器并不能證明每種類型都有l(wèi)ength屬性(比如說 number 類型、boolean 類型)返顺,所以就報(bào)錯(cuò)了禀苦。

這種情況可以使用泛型約束了

function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

const arr = loggingIdentity([1, 2, 3])

上面代碼中約束了傳入的參數(shù)需要是任意類型的數(shù)組,但 Object遂鹊,String類型都是有l(wèi)ength屬性的振乏,這時(shí)候就不滿足這種場(chǎng)景了。這時(shí)候需要對(duì)泛型進(jìn)行約束秉扑,允許這個(gè)函數(shù)傳入包含length屬性的變量(約束泛型):

interface Lengthwise {
  length: number
}

// type Lengthwise = {
//   length: number
// }

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg
}

const len01 = loggingIdentity('abc') // 3
const len02 = loggingIdentity({ length: 12 }) // 12
const len03 = loggingIdentity([1, 2, 3]) // 3

上面代碼中慧邮,定義了一個(gè)接口 Lengthwise ,在函數(shù) loggingIdentity 的泛型中使用 extends 關(guān)鍵字繼承了Lengthwise 接口舟陆,這樣調(diào)用函數(shù) loggingIdentity 時(shí)傳的參數(shù)必須要有 length 這個(gè)屬性

索引類型(keyof)
官方定義:keyof該操作符可以用于獲取某種類型的所有鍵误澳,其返回類型是聯(lián)合類型(keyof的應(yīng)用多第三方庫(kù)的源碼中常見)
聲明一個(gè)類型參數(shù),且它被另一個(gè)類型參數(shù)所約束秦躯。 比如忆谓,現(xiàn)在我們想要用屬性名從對(duì)象里獲取這個(gè)屬性。 并且我們想要確保這個(gè)屬性存在于對(duì)象obj上踱承,因此我們需要在這兩個(gè)類型之間使用約束倡缠。

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

js中的Object.keys大家肯定都用過, 獲取對(duì)象的鍵值, ts中的keyof和他類似, 可以用來獲取對(duì)象類型的鍵值:

type A = keyof {a:1, b:'123'} // 'a'|'b'
type B = keyof [1, 2] // '1'|'2'|'push'...  獲取到內(nèi)容的同時(shí), 還得到了Array原型上的方法和屬性(實(shí)戰(zhàn)中暫時(shí)沒遇到這種需求, 了解即可)
可以獲得鍵值, 也可以獲取對(duì)象類型的值的類型:

type C = A['a'] // 等于type C = 1;
let c:C = 2 // 錯(cuò)誤, 值只能是1

多重約束&交叉類型
示例1
交叉類型是將多個(gè)類型合并為一個(gè)類型米母。 這讓我們可以把現(xiàn)有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性毡琉。 例如铁瞒,Person & Serializable & Loggable同時(shí)是PersonSerializableLoggable。 就是說這個(gè)類型的對(duì)象同時(shí)擁有了這三種類型的成員桅滋。

我們大多是在混入(mixins)或其它不適合典型面向?qū)ο竽P偷牡胤娇吹浇徊骖愋偷氖褂谩?(在JavaScript里發(fā)生這種情況的場(chǎng)合很多;鬯!) 下面是如何創(chuàng)建混入的一個(gè)簡(jiǎn)單例子:

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();

示例2

interface Sentence {
  content: string;
  title: string;
}
interface Music {
  url: string;
}
class Test<T extends Sentence & Music>{
  props: T
  constructor(public arg: T) {
    this.props = arg
  }
  info() {
    return {
      url: this.props.url,
      content: this.props.content,
      title: this.props.title
    }
  }
}

let a = new Test({
  title: "泛型",
  content: "xasdjoasdoijasiodhoia",
  url: "aaaaaaaaaaaa"
})
console.log(a);

在泛型里使用類類型
在TypeScript使用泛型創(chuàng)建工廠函數(shù)時(shí),需要引用構(gòu)造函數(shù)的類類型丐谋。比如芍碧,

1、參數(shù)c的注解是一個(gè)對(duì)象号俐,可以把它看成一個(gè)接口泌豆;
2、對(duì)象里面的屬性是一個(gè)方法( new() )吏饿,方法的返回值是 (T)
        可以寫成 function create<T>(c: new() => T): T { return new c(); }
3踪危、有一個(gè) new 方法說明這個(gè)方法是個(gè)構(gòu)造方法,那么 T 就代表構(gòu)造方法的實(shí)例
4猪落、返回的是實(shí)例 T贞远,所以需要調(diào)用 new方法來獲取實(shí)例
function create<T>(c: {new(): T; }): T {
  return new c();
}

不要亂用泛型

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

function convert<T>(input:T[]):number{
    return input.length;
}

這樣用泛型就沒有什么意義了, 和any類型沒有什么區(qū)別.

TypeScript 中提供了許多常用的泛型工具類型,這些工具類型可以幫助我們更輕松地定義通用的類型映射和轉(zhuǎn)換笨忌。以下是一些常見的 TypeScript 泛型工具類型:

Partial<T>:將類型 T 的所有屬性設(shè)置為可選蓝仲。
Required<T>:將類型 T 的所有屬性設(shè)置為必選。
Readonly<T>:將類型 T 的所有屬性設(shè)置為只讀官疲。
Record<K, T>:創(chuàng)建一個(gè)具有指定鍵類型 K 和值類型 T 的對(duì)象類型袱结。
Pick<T, K>:從類型 T 中選擇部分屬性,僅包含鍵為 K 的屬性途凫。
Omit<T, K>:從類型 T 中排除某些屬性垢夹,即移除鍵為 K 的屬性。
Exclude<T, U>:從類型 T 中排除可以賦值給 U 的類型颖榜。
Extract<T, U>:從類型 T 中提取可以賦值給 U 的類型棚饵。
NonNullable<T>:從類型 T 中剔除 null 和 undefined 類型。
ReturnType<T>:獲取函數(shù)類型 T 的返回類型掩完。
Parameters<T>:獲取函數(shù)類型 T 的參數(shù)類型組成的元組類型噪漾。

泛型的使用場(chǎng)景
大神的泛型講解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市且蓬,隨后出現(xiàn)的幾起案子欣硼,更是在濱河造成了極大的恐慌,老刑警劉巖恶阴,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诈胜,死亡現(xiàn)場(chǎng)離奇詭異豹障,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焦匈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門血公,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缓熟,你說我怎么就攤上這事累魔。” “怎么了够滑?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵垦写,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我彰触,道長(zhǎng)梯投,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任况毅,我火速辦了婚禮分蓖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俭茧。我一直安慰自己咆疗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布母债。 她就那樣靜靜地躺著,像睡著了一般尝抖。 火紅的嫁衣襯著肌膚如雪毡们。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天昧辽,我揣著相機(jī)與錄音衙熔,去河邊找鬼。 笑死搅荞,一個(gè)胖子當(dāng)著我的面吹牛红氯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咕痛,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼痢甘,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了茉贡?” 一聲冷哼從身側(cè)響起塞栅,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎腔丧,沒想到半個(gè)月后放椰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體作烟,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年砾医,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壹置。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捺典,死狀恐怖爽雄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怖亭,我是刑警寧澤涎显,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站兴猩,受9級(jí)特大地震影響期吓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜倾芝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一讨勤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晨另,春花似錦潭千、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至路翻,卻和暖如春狈癞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茂契。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工蝶桶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掉冶。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓真竖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親厌小。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恢共,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容