高级类型
- 交叉类型
- 联合类型
- 索引类型
- 映射类型
- 条件类型
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 | Week
function 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.side
case 'Rectangle':
return s.height * s.width
}
}
类型never类型,在switch中,将未捕获到的类型赋值给never,检查s是否是never类型。
function area(s: Shape) {
switch (s.kind){
case 'Square':
return s.side * s.side
case '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,b
interface obj {
age: number
name: 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 : T
type 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和null
type NotNull<T> = Diff<T, undefined | null>
// 过滤掉undefined和null,T5的类型就变成了string和number
type 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> & U
type 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