高级类型
- 交叉类型
- 联合类型
- 索引类型
- 映射类型
- 条件类型
TS的交叉类型
将多个类型合并成为一个新的类型,新类型具有所有类型的特性
语法:typeA & typeB
可用于扩展类型,让原类型有更多属性
// 交叉类型interface InterfaceA{methodA(): void;}interface InterfaceB{methodB(): void;}let aAndb:InterfaceA & InterfaceB = {methodA(){},methodB(){}}console.log(aAndb)
TS的联合类型
联合类型
语法:typeA | typeB , 或者使用enum枚举定义类型合集
interface JavaInterface {helloJava(): void;build(): void;}interface JsInterface {helloJavaScript(): void;build(): void;}class Java implements JavaInterface {helloJava() {console.log("Hello Java")}build(){console.log("build")}}class JavaScript implements JsInterface {helloJavaScript() {console.log("Hello JavaScript")}build(){console.log("build")}}// ts类型保护, 定义联合类型的一种方式const enum Type { Strong, Week };// 定义联合类型的另一种方式,更常用// type tp = Strong | Weekfunction getLanguage(type: Type){let lang = type === Type.Strong ? new Java() : new JavaScript();return lang}// getLanguage方法中lang对象为联合类型,此时lang的类型不能确定是Java还是JavaScript// 只能使用两个对象共有的属性/方法,如:build();// 此时调用helloJava或helloJavaScript则报错
可区分的联合类型
如果函数参数的类型是多个类型的联合类型,且多个类型间有一个共用属性,可以利用共用属性,创建出不同的类型保护区块
利用共用属性,创建出类型保护
interface Square {kind: "Square";side: number;}interface Rectangle {kind: "Rectangle";width: number;height: number;}interface Circle{kind: "Circle";radius: number;}type Shape = Square | Rectangle | Circle;// 由于此时没有设置Circle类型的判断,如果s为Circle类型则会返回undefined。// TS报错:Function lacks ending return statement and return type does not include 'undefined'.function area(s: Shape): number {switch (s.kind){case 'Square':return s.side * s.sidecase 'Rectangle':return s.height * s.width}}
类型never类型,在switch中,将未捕获到的类型赋值给never,检查s是否是never类型。
function area(s: Shape) {switch (s.kind){case 'Square':return s.side * s.sidecase 'Rectangle':return s.height * s.width// 当缺少对Circle类型判断时,说明可以进入到default,s是Circle类型,并不是设置的never类型// TS报错:Argument of type 'Circle' is not assignable to parameter of type 'never'.default:return ((e: never) => {throw new Error(e)})(s)}}
正确的设置switch分支处理,对联合类型的所有类型都进行覆盖
function area(s: Shape) {switch (s.kind){case 'Square':return s.side * s.side;case 'Rectangle':return s.height * s.width;case 'Circle':return (s.radius**2) * Math.PI;default:return ((e: never) => {throw new Error(e)})(s)}}
TS索引类型
索引类型三个概念:
- 查询操作符 keyof
- keyof T:表示类型T,所有公共属性字面量的联合类型
- 访问操作符 T[K]
- T[K]:表示对象T的属性K所表示的类型
- 范型约束 extends
keyof T:表示类型T的所有属性的字面量【即key】的联合类型
typeof obj:typeof可以获取值的类型
// 定义一个接口Obj含有属性a,binterface obj {age: numbername: string}// 定义变量key,类型为keyof obj --转化后,key字面量的联合类型--> "age" | "name"let key: keyof obj = "name" // key的类型为 "age" | "name"// keyof操作类class Person {name: string = "北鸟南游";age: number = 1;}let sname: keyof Person; // sname: "name" | "age"sname = "name";
keyof 操作符除了支持接口和类之外,它也支持基本数据类型:
let K1: keyof boolean; // let K1: "valueOf"let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...let K3: keyof symbol; // let K1: "valueOf"
另外一种特殊情况,定义的接口属性不定。
interface Foo {[props: string]: string;}
此时Foo接口被认定为属性字段全部为string,由于js可以通过数字和字符串访问对象属性,因此 keyof Foo 的结果是string | number;
let f:key Foo = 123; //此时也不报错

如果想通过一个方法getKey获取对象里的属性值,通常的写法
let Boy = {name:"xm",age:10}function getKeys(o:object, k: string){return o[k];}
此时在return o[k]时,TS会报错,类型检验无法通过。
为了解决这个错误,可以使用范型加索引类型
let Boy = {name:"xm",age:10}function getKeys<T extends object, K extends keyof T>(o:T, name:K): T[K]{return o[name];}console.log(getKeys(Boy, "name"));
索引类型访问
语法:T[K],表示对象T的属性K的类型, 和访问对象的属性值类似
interface Person {age: number;}let p:Person['age']; //p:number,p的类型为数字type Man = { name:'man'; age:12}let m:Man['name']; //m的类型为string
范型约束 extends
T extends U:范型T可以继承对象U的属性
function prop<T extends object, K extends keyof T>(obj: T, keys: K): T[K]{return obj[keys];}
TS条件类型
TS提供几种内置的预定义的条件类型
- Exclude
- 用于从类型T中去除不在U类型中的成员【从类型中排除类型, 和omit不同】 - Extract
- 用于从类型T中取出可分配给U类型的成员【从类型中提取类型,和pick不同】 - NonNullable
- 用于从类型T中去除undefined和null类型 - ReturnType
- 获取函数类型的返回类型 - InstanceType
- 获取构造函数的实例类型 条件类型
条件类型是由条件表达式决定的类型,使类型具有不唯一性,增加TS语言灵活性。内置类型主要是通过三元表达式和 范型约束判断实现
T extends U ? X : Y;
如果类型T可被赋值给类型U,那么结果类型是X类型,否则是Y类型。
// 条件类型type TypeName<T> =T extends string ? 'string' :T extends number ? 'number' :T extends boolean ? 'boolean' :T extends undefined ? 'undefined' :T extends Function ? 'Function' :'object'// 定义类型T1为条件类型,传入参数string,指定t1为string类型type T1 = TypeName<string>// 定义类型T2为条件类型,传入参数string[]type T2 = TypeName<string[]>

T2传入的是数组,不在所属的条件中,则T2的类型为最后的object。
分步式条件类型
T是联合类型,结果类型变为多条件类型的联合类型
(A | B) extends U ? X : Y;
将A和B拆解计算后再联合
(A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]>
分步式条件类型应用
对类型进行过滤
如果T可以被赋值给U,结果类型为never类型,否则为T类型
// 如果T可以被赋值给U,结果类型为never类型,否则为T类型type Diff<T, U> = T extends U ? never : Ttype T4 = Diff<'a' | 'b' | 'c', 'a' | 'x'>
拆解逻辑分析
Diff会被拆解为多个条件类型的联合类型
type Diff<T, U> = T extends U ? never : T; //和内置条件类型Exclude一样type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>// 拆解分析:// Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>// never | 'b' | 'c'// 'b' | 'c'
- 先判断a是否可以被赋值给这个字面量联合类型’a’ | ‘e’,答案是可以的,所以返回never
- 继续,因为b不可以被赋值给字面量联合类型’a’ | ‘e’,所以返回b
- 继续,c不可以被赋值给’a’ | ‘e’,所以返回c
- 最后,never和b,c的联合类型为’b’ | ‘c’
可以实现从类型T中移除不需要的类型,如undefined和null,定义一个NotNull,从T中过滤掉undefined和null
// Diff扩展:从T中过滤掉undefined和nulltype NotNull<T> = Diff<T, undefined | null>// 过滤掉undefined和null,T5的类型就变成了string和numbertype T5 = NotNull<string | number | undefined | null>

上述实现过程,在TS中有内置的方法:Extract和Exclude和NonNullable以及ReturnType和InstanceType;
- Exclude作用是从类型T中过滤掉可以赋值给类型U的类型
Extract作用是可以从类型T中抽取出可以赋值给U的类型
// Extract<T, u>和Exclude<T, U>type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>type T7 = Exclude<'a' | 'b' | 'c', 'a' | 'e'>
infer关键字 【推断】的使用
infer 用来推断值的类型,放在什么位置就是推断哪里的结果,多用在函数的参数或返回值中
- infer要配合 extends 关键字使用,能自动推断出结果
定义的函数类型 (…args: any[]) => any
function getPos(x:number, y:number){return {posX: x+'px', posY: y+ 'px'}}/* 获取函数参数类型 infer P 推导函数参数类型 */type MyParameters<T extends (...args: any[]) => any> =T extends (...args: infer P) => any // infer P用来临时存放函数参数类型,如果有值则是P,否则是any? P : any;// Parameters 推导出 参数的类型type getPosParamsType = MyParameters<typeof getPos>; // type getPosParamsType = [x: number, y: number]/* 获取函数返回值类型 infer R 推导函数返回值类型 */type MyReturnType<T extends (...args: any[]) => any> =T extends (...args: any) => infer R // infer R用来临时存放函数返回值类型,如果有值则是R,否则是any? R : any;// ReturnType 推导出 返回值的类型type getPosType = MyReturnType<typeof getPos> // type getPosType = { posX: string; posY: string; }
使用infer提取类型的一部分
提取数组中的第一个
type first<T extends unknown[]> = T extends [infer X, ...infer Y] ? X : any;// 获取元组的第一个值type res = first<[1,2,3]>; // type res = 1
注意,第一个 extends 不是条件而是约束,条件类型是 extends ? : ,表示约束类型参数只能是数组类型
TS中内置条件类型源码分析
Extract和Exclude的源码分析
- Extract:从类型中,提取指定类型
- Exclude:从类型中排除指定类型
```typescript
/* Exclude from T those types that are assignable to U /
type Exclude
= T extends U ? never : T;
/* Extract from T those types that are assignable to U /
type Extract
/* 使用 /
type originType = string | number | boolean;
type excludeType = Exclude
<a name="kW1IK"></a>##### NonNullable源码,排除掉所有空的类型```typescript// NonNullable,排除掉所有空的类型type T9 = NonNullable<string | number | undefined | null>

/** Exclude null and undefined from T */type NonNullable<T> = T extends null | undefined ? never : T;
Parameters 获取函数参数类型
function getPos(x:number, y:number){return {posX: x+'px', posY: y+ 'px'}}/* 获取函数参数类型 infer P 推导函数参数类型 */type MyParameters<T extends (...args: any[]) => any> =T extends (...args: infer P) => any // infer P用来临时存放函数参数类型,如果有值则是P,否则是any? P : any;// Parameters 推导出 参数的类型type getPosParamsType = MyParameters<typeof getPos>;// 转化后getPosParamsType = [x:number, y:number]
ReturnType 获取函数返回值的类型
// ReturnType<T>获取函数返回值的类型type T8 = ReturnType<() => string>

TS中ReturnType源码:【使用 infer 关键字,为推断的意思,可以js中let/var定义变量类似】
/** Obtain the return type of a function type */type ReturnType<T extends (...args: any) => any>= T extends (...args: any) => infer R ? R : any;
实现分析:
T extends (…args: any) => any : ReturnType要求参数 T 可以赋值给一个函数,这个函数有任意的参数,返回值类型也是任意的
由于函数返回值类型不确定,这里使用infer关键字,表示待推断,延迟推断,需要根据实际的情况确定
infer R ? R : any
如果实际类型是R,那么结果类型就是R,否则返回值类型就是any
InstanceType:返回构造函数的类型
class ClassDemo {constructor(x:number,y:string){}}// typeof ClassDemo获取类的类型type CType = InstanceType<typeof ClassDemo> ;

TypeScript源码的实现
/** Obtain the return type of a constructor function type */type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
ConstructorParameters返回构造函数参数的类型
ConstructorParameters
传入一个将类转化后的类型参数T
/** 源码 */type MyConstructorParameters<T extends new (...args: any[]) => any>= T extends new (...args: infer C) // 使用infer C对类的构造函数中参数进行推断=> any ? C : any;/** 使用 */class ClassDemo {constructor(x:number,y:string){}}// typeof ClassDemo获取类的类型type CType = MyConstructorParameters<typeof ClassDemo>;//转化后, type CType = [x: number, y: string]let ct: CType = [1,'2']
TS的高级类型-映射类型
映射类型:TS允许将一个类型映射成另外一种类型
有以下Readonly、Partial、Pick三种同源映射,和Record非同源映射。
Readonly 只读
将一个接口的所有属性映射为只读
interface Obj{a:number;b:string;c:boolean;}type ReadonlyObj = Readonly<Obj>/* TS编译之后type ReadonlyObj = {readonly a: number;readonly b: string;readonly c: boolean;} */let ReadonlyObjDemo:ReadonlyObj = {a:1,b:"readonly",c:true}ReadonlyObjDemo.a = 3;//error Cannot assign to 'a' because it is a read-only property.
type Readonly<T> = {readonly [K in keyof T] : T[K]}
Required 必选
Requied
interface requiredType{name: string;age?: number;isBoy?: boolean;}// 会将age和isBoy变为必填属性let requiredObj: Required<requiredType> = {name: "北鸟南游",age: 18,isBoy: true}
type Partial<T> = {[P in keyof T] -?: T[P]; // 通过 -? 将可选属性变为必填属性};
Partial 可选
将一个接口的所有属性映射为可选.
interface Obj{a:number;b:string;c:boolean;}type PartialObj = Partial<Obj>/* TS编译之后type PartialObj = {a?: number | undefined;b?: string | undefined;c?: boolean | undefined;} */// a/b/c三个属性都是可选的let PartialObjDemo:PartialObj = {a:100}
/*** Make all properties in T optional*/type Partial<T> = {[P in keyof T]?: T[P];};type MyPartial<T> = { [K in keyof T]?: T[K]};type partObj = MyPartial<Obj>;
定义深层Partial, DeepPartial
将内部是对象类型继续执行Partial
interface Obj{a:number;b:string;c:boolean;d: Bar;}interface Bar {x:number;y:string;}type DeepPartial<T> = { [K in keyof T]? :T[K] extends object ? //使用extends判断T[K]是否是对象MyPartial<T[K]> : T[K]}type DeepObj = DeepPartial<Obj>;let dpo:DeepObj = {a:1, d: {x:2}}; //这是d赋值几个参数都可以
Pick 摘取部分
摘取对象的一部分属性,形成新类型
语法:
和Extract的不同:
- Extract是两个类型参数,从类型中挑选类型
- Pick是从对象的属性中挑选,形成新的类型
interface Obj{a:number;b:string;c:boolean;}type PickObj = Pick<Obj, "a" | "b"> //抽取了Obj对象的a、b属性,形成一个新的类型/* TS编译之后type PickObj = {a: number;b: string;} */let pickObjDemo:PickObj = {a:66,b:'66'}/** MyPick 源码 **/// 通过 [X in K]循环K中的所有类型type MyPick<T, K extends keyof T> = { [X in K] : T[X]};type pickType = MyPick<Obj, "a"> // 转化后 type pickType = { a: number; }
Omit 排除部分属性的类型
排除对象中的一部分属性,Omit
和Exclude的区别
- Exclude是排除类型中的类型
- Omit是排除对象上的属性,获取到的类型
type OmitObj = Omit<Obj, "a" | "b"> //排除Obj对象的a、b属性,形成一个新的类型/* TS编译之后type OmitObj = {c: boolean;} */let omitObjDemo:OmitObj = {c:true}
// 第一步先从对象T中 排除掉设置的属性K Exclude<keyof T, K>// keyof T是获取对象中的key值,从所有key中排除设置的K// 第二步,Pick剩下的属性组成的类型type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;type omitType = MyOmit<Obj, "a">
非同源映射Record,用来描述对象类型
引入新属性,将引入的属性的类型,设置为第二个参数。
语法: Record
/* Record 源码*/// [P in K] 把K循环一次,// 然后设置为T类型type Record<K extends keyof any, T> = {[P in K]: T;};// K为新增的属性,T为属性类型type RecordObj = Record<"x" | "y", Obj>// 定义一个对象包含x/y属性,这两个属性的类型为Obj类型/* TS编译之后type RecordObj = {x: Obj;y: Obj;} */let RecordObjDemo:RecordObj = {x:{a:1,b:"x",c:true},y:{a:2,b:"y",c:false},}
自定义类型
Diff 差集类型
获取类型T和类型U的差值,就是类型T中包含的类型,但是U中不包含。
let p1 = {name: "p1",age:1,address: "shanghai"}let p2 = {address: "beijing",height: 100}type Diff<T extends object, U extends object> = Omit<T, keyof U>;type p1DiffP2 = Diff<typeof p1, typeof p2>; // 差值之后 type p1DiffP2 = { name: string; age: number; }
Inter交集
获取
let p1 = {name: "p1",age:1,address: "shanghai"}let p2 = {address: "beijing",height: 100}// 报错 类型“keyof U”不满足约束“keyof T”,类型U中包含了T中不存在的类型// type Inter<T extends object, U extends object> = Pick<T,keyof U>;// 正确写法; 首先确保Pick第二个参数挑选的类型存在于第一个参数,// Extract<keyof T, keyof U>获取T中包含U中的类型,所以取出的值肯定包含在T中// Pick源码type Pick<T, K extends keyof T> = { [X in K] : T[X]}type Inter<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U>>;type p1p2Inter = Inter<typeof p1, typeof p2>
MergeType 两个类型的合并
T和U两个类型合并,有重复的类型,以后边的为准。
如果简单使用 & 交叉类型的话,重复出现且类型不一样,就会判断为never
// 两个type类型的合并// 删除T中包含的U中的类型,剩下的就是T中独有的,然后和U进行合并type MergeType<T extends object, U extends object> = Omit<T, keyof U> & Utype t1 = {name: string;age: number;height: string;}type t2 = {age: string;weight: number;}// 展开T类型中的类型,组成一个对象type ComputeType<T> = {[K in keyof T]: T[K]}type mtype = ComputeType<MergeType<t1, t2>>/* type mtype = {name: string;height: string;age: string;weight: number;} */
类型实践操作
装包、拆包。
- proxify对对象的属性进行封装,属性后只能获取到get/set属性
- unProxify对包装的对象拆解,获取到原类型的属性或方法
```typescript
let data = {
name: “test”,
age:1
}
// 可以让代理对象获取到原对象身上的值
type Proxify
= {
[K in keyof T]: {get(): T[K],set(value:any): void}
}
//
function proxify
console.log(proxyData.name.get()) // 这里name后只有get或set属性
function unProxify

