深入講解Ts中高級(jí)類(lèi)型工具

寫(xiě)在最前:本文轉(zhuǎn)自掘金

一、 前置內(nèi)容

[key: string]索引簽名類(lèi)型

索引簽名類(lèi)型主要指的是在接口或類(lèi)型別名中,通過(guò)以下語(yǔ)法來(lái)快速聲明一個(gè)鍵值類(lèi)型一致的類(lèi)型結(jié)構(gòu):

interface Eg1{
  [key: string]: string;
}

keyof 索引查詢(xún)

對(duì)應(yīng)任何類(lèi)型T,keyof T 的結(jié)果為該類(lèi)型上所有共有屬性key的聯(lián)合:

interface Eg1{
  name: string;
  readonly age: number;
}
// T1的類(lèi)型是 'name' | 'age'
type T1 = keyof Eg1

class Eg2 {
  private name: string;
  public readonly age: number;
  protected home: string;
}

// T2實(shí)則約束為 'age'
type T2 = keyof Eg2

T[K] 索引訪問(wèn)

interface Eg1{
  name: string;
  readonly age: number;
}

type V1 = Eg1['name']  // string
type V2 = Eg1['name' | 'age']  // string | number
type V2 = Eg1['name' | 'age222']  // any
type V3 = Eg1[keyof Eg1]  // string | number

T[keyof T] 的方式,可以獲取到T所有key的類(lèi)型組成的聯(lián)合類(lèi)型;注意:如果[]中的key有不存在T中的,則是any;因?yàn)閠s也不知道該key最終是什么類(lèi)型,且不會(huì)報(bào)錯(cuò);

in 映射類(lèi)型

而映射類(lèi)型,就是使用了 PropertyKeys 聯(lián)合類(lèi)型的泛型,其中 PropertyKeys 多是通過(guò) keyof 創(chuàng)建,然后循環(huán)遍歷鍵名創(chuàng)建一個(gè)類(lèi)型:

type Clone<T> = {
  [K in keyof T]: T[K];
};

&交叉類(lèi)型注意點(diǎn)

交叉類(lèi)型取的多個(gè)類(lèi)型的并集,如果相同key但類(lèi)型不同,則該keynever

interface Eg1{
  name: string;
  age: number;
}
interface Eg2{
  color: string;
  age: string;
}
 type T = Eg1 & Eg2  // T的類(lèi)型為{ name: string;age: never; color: string },注意,age因?yàn)閮烧呓涌趦?nèi)的類(lèi)型不一致所有事never
// 可通過(guò)如下實(shí)例驗(yàn)證
const val: T = {
  name: ' ',
  color: ' ' ,
  age: (function a(){ throw Error() })(),
}

extends 關(guān)鍵字特性(重點(diǎn))

特性一,用于接口,表示繼承

interface T1{
  name: string;
}
interface T2{
  sex: number;
}

// T3 = {name: string; sex: number; age:number;}
interface T3 extends T1,T2{
  age: number,
}

注意,接口支持多重繼承,語(yǔ)法為逗號(hào)隔開(kāi)。如果是type實(shí)現(xiàn)繼承,則可以使用交叉類(lèi)型 type A = B& C & D

特性二,表示條件類(lèi)型,可用于條件判斷
表示條件判斷,如果前面的條件滿足,則返回問(wèn)號(hào)后的第一個(gè)參數(shù),否則第二個(gè)。類(lèi)似于js 的三元運(yùn)算。

A extends B,A為子類(lèi)型,B為父類(lèi)型 ,在接口中,屬性約束寬泛為父類(lèi)型,子類(lèi)型應(yīng)該繼承父類(lèi)型所有屬性并加以更多屬性約束。 在聯(lián)合類(lèi)型中,類(lèi)型約束越寬泛為父類(lèi)型,子類(lèi)型應(yīng)繼承父類(lèi)型基礎(chǔ)上,縮減類(lèi)型。

type A1 = 'x' extends 'x' ? 1: 2;  //  A1 = 1

type A2 = 'x' | 'y' extends 'x' ? 1: 2;  // A2 = 2

type P<T> = T extends 'x' ? 1: 2;
type A3 = P<'x' | 'y'>  // A3 = 1 | 2

為什么A2A3的值不一樣:

  • 如果用于簡(jiǎn)單的條件判斷,則是之間判斷前面的類(lèi)型是否可分配給后面的類(lèi)型
  • extends前面的類(lèi)型是泛型,且泛型傳入的是聯(lián)合類(lèi)型時(shí),則會(huì)依次判斷該聯(lián)合類(lèi)型的所有子類(lèi)型是否可分配給extends后面的類(lèi)型(是一個(gè)分發(fā)的過(guò)程)。

總結(jié),就是extends 前面的參數(shù)為聯(lián)合類(lèi)型時(shí)則會(huì)分解(依次遍歷所有的子類(lèi)型進(jìn)行條件判斷)聯(lián)合類(lèi)型進(jìn)行判斷,然后最終結(jié)果組成新的聯(lián)合類(lèi)型。
如果再?lài)?yán)謹(jǐn)一些,其實(shí)我們就得到了官方的解釋?zhuān)?strong>對(duì)于屬于裸類(lèi)型參數(shù)的檢查類(lèi)型,條件類(lèi)型會(huì)在實(shí)例化時(shí)期自動(dòng)分發(fā)到聯(lián)合類(lèi)型上。如果不想被分發(fā),可以通過(guò)簡(jiǎn)單的元組類(lèi)型包裹一下,就非裸類(lèi)型參數(shù)了:

type P<T> = [T] extends ['x'] ? 1 : 2;

type A4 = p<'x'|'y'>  // 2

我們除了可以使用數(shù)組元組包裹,還可以:

type NoDistribute<T> = T & {};

type Wrapped<T> = NoDistribute<T> extends boolean ? "Y" : "N";

type A1 = Wrapped<number | boolean>; // "N"
type A2 = Wrapped<true | false>; // "Y"
type A3 = Wrapped<true | false | 599>; // "N"

這里有兩個(gè)需要單獨(dú)提出來(lái)的特殊情況,anynever,any作為參數(shù),判斷條件非any情況下會(huì)返回判斷結(jié)果的聯(lián)合類(lèi)型;

// 直接使用,返回聯(lián)合類(lèi)型
type Tmp1 = any extends string ? 1 : 2;  // 1 | 2

type Tmp2<T> = T extends string ? 1 : 2;
// 通過(guò)泛型參數(shù)傳入,同樣返回聯(lián)合類(lèi)型
type Tmp2Res = Tmp2<any>; // 1 | 2

// 如果判斷條件是 any,那么仍然會(huì)進(jìn)行判斷
type Special1 = any extends any ? 1 : 2; // 1
type Special2<T> = T extends any ? 1 : 2;
type Special2Res = Special2<any>; // 1

never作為泛型參數(shù)是會(huì)返回never

// 直接使用,仍然會(huì)進(jìn)行判斷
type Tmp3 = never extends string ? 1 : 2; // 1

type Tmp4<T> = T extends string ? 1 : 2;
// 通過(guò)泛型參數(shù)傳入,會(huì)跳過(guò)判斷
type Tmp4Res = Tmp4<never>; // never

// 如果判斷條件是 never,還是僅在作為泛型參數(shù)時(shí)才跳過(guò)判斷
type Special3 = never extends never ? 1 : 2; // 1
type Special4<T> = T extends never ? 1 : 2;
type Special4Res = Special4<never>; // never

類(lèi)型兼容性

集合論中,如果一個(gè)集合的所有元素在集合B中都存在,則A是B的子集;
類(lèi)型系統(tǒng)中,如果一個(gè)類(lèi)型的屬性更具體,則該類(lèi)型是子類(lèi)型。(因?yàn)閷傩愿賱t說(shuō)明該類(lèi)型約束更寬泛,是父類(lèi)型)

因此,我們得到基本結(jié)論:子類(lèi)型比父類(lèi)型更加具體,父類(lèi)型比子類(lèi)型更寬泛。下面我們也將基于類(lèi)型的可賦值性、協(xié)變、逆變、雙向協(xié)變等進(jìn)一步講解。

可賦值性 子類(lèi)型可賦值給父類(lèi)型,反之不行

interface Animal {
  name: string;
}

interface Dog extends Animal {
  break(): void;
}

let a: Animal;
let b: Dog;

a = b // 子類(lèi)可以賦值給更加寬泛的父類(lèi)型
b = a // 反過(guò)來(lái)不行

可賦值性在聯(lián)合類(lèi)型中的特性

type A = 1 | 2 | 3
type B = 2 | 3
let a: A;
let b: B;

a = b // 可以賦值
b = a // 不可以賦值

是不是A的類(lèi)型更多,A就是子類(lèi)型呢?恰恰相反,A此處類(lèi)型更多但表達(dá)的類(lèi)型越寬泛,所有A是父類(lèi)型,B是子類(lèi)型。因此父類(lèi)型不能給子類(lèi)型賦值。

協(xié)變

interface Animal {
  name: string;
}
interface Dog extends Animal {
  break(): void;
}

let Eg1: Animal;
let Eg2: Dog;
// 兼容,可以賦值
Eg1 = Eg2

let Eg3: Array<Animal>
let Eg4: Array<Dog>
// 兼容,可以賦值
Eg3 = Eg4

通過(guò)Eg3Eg4來(lái)看,在AnimalDog在變成數(shù)組后,Array<Dog>依舊可以賦值給Array<Animal>,因此對(duì)于type MakeArray = Array<any>來(lái)說(shuō)就是協(xié)變。
引用維基百科中的定義:

協(xié)變與逆變(Convariance and contravariance)是在計(jì)算機(jī)科學(xué)中,描述具有父/子型別關(guān)系的多個(gè)型別通過(guò)型別構(gòu)造器、構(gòu)造出的多個(gè)復(fù)雜型別之間是否有父/子型別關(guān)系的用語(yǔ)。

簡(jiǎn)單說(shuō),具有父子關(guān)系的多個(gè)類(lèi)型,在通過(guò)某種構(gòu)造器構(gòu)造成的新的類(lèi)型,如果還具有父子關(guān)系則是協(xié)變,而關(guān)系逆轉(zhuǎn)了(子轉(zhuǎn)父,父轉(zhuǎn)子)就是逆變。
這種“型變”分為兩種,一種是子類(lèi)型可以賦值給父類(lèi)型,叫做協(xié)變,一種是父類(lèi)型可以賦值給子類(lèi)型,叫做逆變。

逆變

interface Animal {
  name: string;
}

interface Dog extends Animal {
  break(): void;
}

type AnimalFn = (arg:Animal) => void
type DogFn = (arg: Dog) => void

let Eg1: AnimalFn;
let Eg2: DogFn;

Eg1 = Eg2; // 不可賦值
Eg2 = Eg1; //可賦值

理論上,Animal = Dog是類(lèi)型安全的,那么AnimalFn = DogFn也應(yīng)該類(lèi)型安全猜對(duì),為什么ts認(rèn)為不安全呢?看下面例子:

let animal: AnimalFn = (arg: Animal) => {}
let dog: DogFn = (arg: Dog)=>{ arg.break() }

// 假設(shè)類(lèi)型安全可以賦值
animal = dog;
// 那么animal 在調(diào)用時(shí)約束的參數(shù)缺少dog所需要的參數(shù),此時(shí)會(huì)導(dǎo)致錯(cuò)誤
// animal = (arg)=>{arg.break()}
animal({name: 'cat'});

從這個(gè)例子看到,如果dog函數(shù)賦值給animal函數(shù),那么animal函數(shù)在調(diào)用時(shí),約束的參數(shù)是Animal,但animal實(shí)際為dog的調(diào)用,傳入?yún)?shù)無(wú)break()方法,此時(shí)就會(huì)出現(xiàn)錯(cuò)誤。
因此,AnimalDog在進(jìn)行type Fn<T> = (arg: T) => void 構(gòu)造器構(gòu)造后,父子關(guān)系就逆轉(zhuǎn)了,此時(shí)稱(chēng)為逆變。

雙向協(xié)變
ts在函數(shù)參數(shù)的比較中實(shí)際上默認(rèn)采取的策略就是雙向協(xié)變:只有當(dāng)源函數(shù)參數(shù)能夠賦值給目標(biāo)函數(shù)或者反過(guò)來(lái)時(shí)才能賦值成功。

這是不穩(wěn)定的,因?yàn)檎{(diào)用者可能傳入了一個(gè)具有更精確類(lèi)型信息的函數(shù),但是調(diào)用這個(gè)傳入的函數(shù)的時(shí)候卻使用了不是那么精確的類(lèi)型信息(典型的就是上述的逆變)。但是實(shí)際上,這極少會(huì)發(fā)生錯(cuò)誤,并且能夠?qū)崿F(xiàn)很多JavaScript里常見(jiàn)模式:

// lib.dom.d.ts 中EventListener 的接口定義
interface EventListener{
  (evt: Event): void;
}
//  簡(jiǎn)化后的Event
interface Event {
  readonly target: EventTarget | null;
  preventDefault(): void;
}
// 簡(jiǎn)化合并后的MouseEvent
interface MouseEvent extends Event{
  readonly X: number;
  readonly Y: number;
}
// 簡(jiǎn)化后的window接口
interface window{
  // 簡(jiǎn)化后的addEventListener
  addEventListerner(type: string,listener: EventListener)
}

// 日常使用
window.addEventListener('click', (e: Event)=> {})
window.addEventListener('mouseover', (e:MouseEvent) => {})

可以看到windowlistener函數(shù)要去參數(shù)必須是Event,但是日常使用時(shí)更多時(shí)候傳入的是Event子類(lèi)型。但這里可以正常使用,正式其默認(rèn)行為是雙向協(xié)變的原因??梢酝ㄟ^(guò)tsconfig.js中修改strictFunctionType屬性來(lái)嚴(yán)格控制協(xié)變和逆變。

重點(diǎn),infer關(guān)鍵詞的功能暫時(shí)先不做詳細(xì)說(shuō)明,主要是用于extends的條件類(lèi)型中讓ts自己推斷類(lèi)型,具體的可以查閱官網(wǎng)。但關(guān)于infer的一些容易讓人忽略的重要特性,必須提及一下:

infer推導(dǎo)的名稱(chēng)相同并且都處于逆變的位置,則推導(dǎo)的結(jié)果將會(huì)是交叉類(lèi)型

type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void; } ? U : boolean;

type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;  // string
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;  // never

let Eg1: { a: (x: string | number) => void; b: (x: number | string) => void }  // 父類(lèi)  
let Eg2: { a: (x: string) => void; b: (x: number) => void } // 子類(lèi)
// 允許父類(lèi)向子類(lèi)賦值,為逆變,推到結(jié)果為交叉類(lèi)型 never

infer推導(dǎo)的名稱(chēng)相同并且都處于協(xié)變的位置,則推導(dǎo)的結(jié)果將會(huì)是聯(lián)合類(lèi)型

type Foo<T> = T extends { a: infer U; b: infer U } ? U : never;

type T3 = Foo<{ a: string; b: string }>;    // string
type T4 = Foo<{ a: number; b: string }>;    // string|number

let Eg3: { a: number | string; b: number | string }  // 父類(lèi)
let Eg4: { a: number; b: string }  // 子類(lèi)
// 允許子類(lèi)向父類(lèi)賦值,為協(xié)變,推到結(jié)果為聯(lián)合類(lèi)型 number| string

第二部分 ts內(nèi)置類(lèi)型工具原理解析

Partial

Partial<T>T的所有類(lèi)型變?yōu)榭蛇x的。

// 核心實(shí)現(xiàn)就是通過(guò)映射類(lèi)型遍歷T上所有的屬性,
// 然后將每個(gè)屬性設(shè)置為可選屬性
···
type Partial<T> = {
  [P in keyof T]?: T[P];
}
  • [P in keyof T]通過(guò)映射類(lèi)型,遍歷T上的所有屬性
  • ?:設(shè)置屬性為可選的
  • T[P]設(shè)置類(lèi)型為原來(lái)的類(lèi)型

擴(kuò)展一下,將制定的key變成可選類(lèi)型

/**
 *主要通過(guò)K extends keyof T 約束K必須為keyof T的子類(lèi)
 *keyof T得到的是T的所有key組成的聯(lián)合類(lèi)型
 */
type PartialOptional<T, K extends keyof T>=P{
  [P in K]?:T[P];
}
/**
 *@example 
 *    type Eg1 = {key1?: string; key2?: number}
 */
type Eg1 = PartialOptional<{
  key1:string;
  key2:number;
  key3: '';
}, 'key1'|'key2'>

Readonly

/*
 *主要通過(guò)映射遍歷所有key,
 *然后給每個(gè)key增加一個(gè)readonly修飾符
 */
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

/*
 * @example
 * type Eg1 = { readonly key1: string; readonly key2: number; }
 */
type Eg1 = Readonly<{
  key1: string;
  key2: number;
}>

Pick

挑選一組屬性并組成一個(gè)新的類(lèi)型。

type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

/* @example
 *  type Eg = {key1:string;key3:boolean;}
 *
 */
type Eg = Pick<{
  key1:string;
  key2: number;
  key3: boolean;
}, 'key1'|'key3'>

Record

構(gòu)造一個(gè)typekey 為聯(lián)合類(lèi)型中的每個(gè)子類(lèi)型,類(lèi)型為T

/**
 * @example
 * type Eg = { a: {key1: string}; b: {key2: string} }
 */
type Eg = Record<'a' | 'b', {key1: string}>

Record具體實(shí)現(xiàn)

// k作為key,所有的類(lèi)型僅為三種string|number|symbol ,使用keyof any表示
type Record<K extends keyof any, T> = {
  [P in K]: T
}

其實(shí),Record<string, unknown>Record<string, any>是日常使用較多的形式,通常我們使用這兩者來(lái)代替 object 類(lèi)型。

擴(kuò)展:同態(tài)與非同態(tài)。

  • PartialReadonlyPick都屬于同態(tài),即其實(shí)現(xiàn)需要輸入類(lèi)型T來(lái)拷貝屬性,因此屬性修飾符(例如readonly ?:)都會(huì)被拷貝。可從下面栗子驗(yàn)證:
// type Eg = {readonly a?: string}
type Eg = Pick<{readonly a?: string}, 'a'>

Eg的結(jié)果來(lái)看,Pick在拷貝屬性時(shí),連帶拷貝了readonly?:修飾符。

  • Record 是非同態(tài)的,不需要拷貝屬性,因此不會(huì)拷貝屬性修飾符
    根據(jù)Pick的實(shí)現(xiàn),P in keyof any并沒(méi)有拷貝傳入類(lèi)型的屬性,而其他幾個(gè)工具無(wú)一例外,都是用了P in keyof T來(lái)輔助拷貝傳入類(lèi)型的屬性。

Exclude原理解析

Exclude<T, U>提取存在于T,但不存在與U的類(lèi)型組成的聯(lián)合類(lèi)型。

/*
 * 遍歷T中的所有子類(lèi)型,如果該子類(lèi)型約束于U(存在于U,兼容于U)
 * 則返回never類(lèi)型,否則返回該子類(lèi)型
 */
  type Exclude<T, U> = T extends U ? never: T
/*
 * @example
 *   type Eg = 'key1'
 */
 type Eg = Exclude<'key1'| 'key2', 'key2'>

注意

  • nerver表示一個(gè)不存在的類(lèi)型
  • nerver與其他類(lèi)型的聯(lián)合后,是沒(méi)有nerver
// type Eg2 = string | number
type Eg2 = string | number | nerver

因此上述Eg其實(shí)就等于key1 | never,也就是key1

Extract

Extract<T, U> 提取聯(lián)合類(lèi)型T和聯(lián)合類(lèi)型U的所有交集。

/*
 * 遍歷T中的所有子類(lèi)型,如果該子類(lèi)型約束于U(存在于U,兼容于U)
 * 則返回該子類(lèi)型,否則返回never
 */
  type Extract<T, U> = T extends U ?  T: never
/*
 * @example
 *   type Eg = 'key2'
 */
 type Eg = Extract<'key1'| 'key2', 'key2'>

Omit原理分析

Omit<T, K>從類(lèi)型T中剔除K中所有屬性。

type Omit<T, K> = Pick<T ,Exclude<keyof T, K>>
/*
 * @example
 *  Eg = { key2: number; key3: boolean; }
 */
type Eg = Omit1<{key1:string;key2:number;key3:boolean},'key12'|'key1'|'key13'>
  • 首先我們可以利用Pick提取我們需要的keys組成的類(lèi)型
  • 也就是Omit = Pick<T, 我們需要的屬性聯(lián)合>
  • 而我們所需要的屬性聯(lián)合,就是從T的屬性聯(lián)合中排除存在于聯(lián)合類(lèi)型K中的
  • 也就是Exclude<keyof T, K>

Parameters

Parameters 獲取函數(shù)的參數(shù)類(lèi)型,將每個(gè)參數(shù)類(lèi)型放進(jìn)一個(gè)元組中。

// 具體實(shí)現(xiàn)
type Parameters<T extends (...args: any) = >any>  =  T extends (...args: infer P) => any ? P :never;

// type Eg = [ arg1: string, arg2: number ]
type Eg = Parameters<(arg1: string, arg2: number) => void>;
  • Parameters 首選約束T必須是一個(gè)函數(shù)類(lèi)型,所以(..args: any) => any替換成Function也可以
  • 判斷T是否是函數(shù)類(lèi)型,如果是則使用inter P讓ts自己去推導(dǎo)函數(shù)的參數(shù)類(lèi)型,并將推導(dǎo)的結(jié)果存到類(lèi)型P上,否則就返回never
  • infer關(guān)鍵字作用是讓ts自己推導(dǎo)類(lèi)型,并將推導(dǎo)結(jié)果存儲(chǔ)在其綁定的類(lèi)型上。infer P就是將結(jié)果存在類(lèi)型P上供使用
  • infer關(guān)鍵字只能在extends 條件類(lèi)型上使用,不能在其他地方使用。
  • type Eg = [ arg1: string, arg2: number ] 這是一個(gè)元組,但和我們常見(jiàn)的元組不同,可以理解成具名元組。實(shí)質(zhì)上沒(méi)有什么特殊作用,比如無(wú)法通過(guò)這個(gè)額具名去取值。個(gè)人覺(jué)得,多了語(yǔ)義化的表達(dá)罷了。
  • 定義元組的可選項(xiàng),只能在最后定義
// 普通元組
type Tuple1 = [ string, number? ];
let a: Tuple1 = [ 'aa', 11 ];
let a2: Tuple1 = [ 'aa' ];

// 具名元組
type Tuple2 = [ name: string, age?: number ];
let b: Tuple2 = [ 'aa', 11 ];
let b2: Tuple2 = [ 'aa' ];

擴(kuò)展:infer 實(shí)現(xiàn)一個(gè)推導(dǎo)數(shù)組所有元素的類(lèi)型

type FalttenArray< T extends Arrary<any> > = T extends Arrary<infer P> ? P : never;

// Eg1 = number | string;
type Eg1 = FalttenArray<[number, string]>

// Eg2 = 1 | 'as'
type Eg2 = FalttenArray<[1 | 'as']>

ReturnType 獲取函數(shù)的返回值類(lèi)型

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

ConstructorParameters

ConstructorParameters 可以獲取類(lèi)的構(gòu)造函數(shù)的參數(shù)類(lèi)型,存在一個(gè)元組中。

/*
 * 核心實(shí)現(xiàn)還是利用infer進(jìn)行推導(dǎo)構(gòu)造函數(shù)的參數(shù)類(lèi)型
 */
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

// @example type Eg = [name: string, sex?: number];
class People {
  constructor(public name: string, sex?: number) {}
}
type Eg = ConstructorParameters<typeof People>
  • 首先約束條件T為擁有構(gòu)造函數(shù)的類(lèi)。注意這里有個(gè)abstract修飾符,等下說(shuō)明。
  • 判斷T是滿足約束的類(lèi)時(shí),利用infer P 自動(dòng)推導(dǎo)出構(gòu)造函數(shù)的參數(shù)類(lèi)型,并最終返回該類(lèi)型。
  • 其中new (...args: any)為構(gòu)造簽名,new (...args: any) => any為構(gòu)造函數(shù)類(lèi)型字面量

那么,為什么要對(duì)T約束為abstract抽象類(lèi)呢?看下面栗子:

class MyClass {}       // 定義一個(gè)普通類(lèi)

abstract class MyAbstractClass {}    // 定義一個(gè)抽象類(lèi)

let c1: typeof MyClass = MyClass  // 可以賦值
let c2: typeof MyClass = MyAbstractClass   // 報(bào)錯(cuò),無(wú)法將抽象構(gòu)造函數(shù)類(lèi)型分配給非抽象構(gòu)造函數(shù)類(lèi)型

let c3: typeof MyAbstractClass = MyClass //可以賦值
let c4: typeof MyAbstractClass = MyAbstractClass  //可以賦值

由此可以看出,可以將抽象類(lèi)(抽象構(gòu)造函數(shù))賦值給抽象類(lèi)或者普通類(lèi),反之不行。

那么,為什么使用typeof 類(lèi)作為類(lèi)型呢,直接使用類(lèi)作為類(lèi)型又有什么區(qū)別呢?

// 定義一個(gè)類(lèi)
class People{
  name: string;
  age: number;
  constructor() {}
}
let p1: People = new People   // 可以賦值
let p2: People = People // 不可以賦值 等號(hào)后面缺少name, age

let p3: typeof People = People  // 可以賦值
let p4: typeof People = new People()  // 不可以賦值,p4缺少prototype

簡(jiǎn)單的理解就是

  • typeof 類(lèi)作為類(lèi)型,需要賦值為類(lèi)本身
  • 類(lèi)作為類(lèi)型,需要賦值為類(lèi)的實(shí)例

最后,只需要對(duì)infer的使用換個(gè)位置,便可以獲取構(gòu)造函數(shù)返回值的類(lèi)型:

type InstanceType<T extends abstract new (...args: any)=> any> = T extends abstract new (...args: any) => infer R ? R :any;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容