类型推断
ts 能根据一些简单的规则推断变量的类型。
从右向左
变量的类型可以由定义推断,类型从右向左流动。
let x = 'jack'; //x 推断是 'string' 类型let y = 18; //y 推断是 'number' 类型x = y; //error,不能将 'string' 赋值给 'number'
底部留出
通过 return 关键字推断返回值的类型。
function sum(x:number, y:number){return x + y;}let z = sum(1, 2); //z 推断是 'number' 类型
从左向右
函数参数、返回值类型也能通过赋值来推断。
type Sum = (x:number, y:number) => number;let sum:Sum = (x,y) => {// x = ''; //error, x、y 推断是 'number' 类型return x + y; //返回值 推断是 'number' 类型}let z = sum(1, 2);
结构化
推断规则也适用于结构化的存在(对象字面量)。
let person = {name: 'jack',age: 18,}let name = person.name;let age = person.age;age = 'a'; //error,不能把 “string” 类型赋值给 “number” 类型
解构
推断规则也适用于解构
let person = {name: 'jack',age: 18,}let {name, age} = person;age = 'a'; //error,不能把 “string” 类型赋值给 “number” 类型let arr = [1,2,3];arr[0] = ''; //error,不能把 “string” 类型赋值给 “number” 类型
DefaultProps
interface DefaultProps {name?:string;age?:number;}let defaultProps: DefaultProps = {// name: 'jack', //即使注释,也不影响defaultProps变量的类型,自然不会影响下边 Props类型。age: 18,}let props = {...defaultProps,home: '北京',}type Props = typeof props;/* 等同于type Props = {home: string,name?: string|undefined,age?: number|undefined,}*/
小心使用返回值
尽管 ts 一般情况下能推断函数的返回值,但是它可能并不是你想要的。
function addOne(a:any){return a + 1;}function sum(a:number, b:number){return a + addOne(b);}let k = sum(1, 2); //k推断 是 any类型
交叉类型
https://www.yuque.com/zhuchaoyang/uugnl6/xa2glt#QBAES
typeof
获取一个变量的类型
// 先定义类型,再定义变量type Person = {name:string;}let p1:Person = {name: 'jack'}// 先定义变量,再定义类型let x = {name: 'jack'}type X = typeof x;let x1:X = {name: 'lili'};
索引访问操作符
可以通过 [] 获取一个类型的子类型
interface Person {name:string;age:number;job: {name:string;};interests: {name:string;level:number;}[];}let x:Person['job'] = {name: 'jack'};let y:Person['interests'][0]['level'] = 1;
keyof 索引类型查询操作符
interface Person {name:string;age:number;gender: 'male' | 'female';}type PersonKey = keyof Person;// => type PersonKey1 = 'name'|'age'|'gender';function getValueBykey(p:Person, key:PersonKey){return p[key];}let val = getValueBykey({name: 'jack',age: 18,gender: 'male',}, 'name')console.log(val); //jack
type keyOfAny = keyof any; //any的key组成的联合类型// => type keyOfAny = string | number | symbol;
type Obj = {name: 'jack', age: 18};type ObjKeys = keyof Obj; //Obj的key组成的联合类型// => type ObjKeys = "name" | "age"type Objvalues = Obj[ObjKeys]; // Obj的value组成的联合类型// => type Objvalues = "name" | 18
映射类型
在定义的时候用 in 操作符去批量定义类型中的属性。
interface Person {name:string;age:number;gender: 'male' | 'female';}// 批量把一个接口中的属性全部变成可选的(Partial 的源码就是这样实现的)type PartialPerson = {// 先通过 “keyof Person” 拿到 Person 所有key的集合;// 再用 in 遍历 所有的key// []表示任意属性[key in keyof Person]?: Person[key];}let p1:PartialPerson={};
通过key的数组获取值的数组
//“K extends keyof T”是泛型K的约束条件,指“K”要满足是“keyof T”的子类型,否则就报错function pick<T, K extends keyof T>(o:T, names:Array<K>):Array<T[K]>{return names.map((n) => o[n]);}let user = {id: 1,name: 'jack',}type User = typeof user;type User2 = keyof User; // => type User2 = 'id' | 'name';let res = pick<User, keyof User>(user, ['id', 'name']);console.log(res); //[ 1, 'jack' ]
条件类型
在定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活。
定义条件类型
interface Fish {name1:string}interface Bird {name3:string}interface Water {name2:string}interface Sky {name4:string}// 如果 T 是 Fish 的子类型,那么类型是Water,否则是Skytype Condition<T> = T extends Fish ? Water : Sky;let c1:Condition<Fish> = {name2: 'jack'}; //传Fish,c1的类型就是 Waterlet c2:Condition<Bird> = {name4: 'jack'}; //传Bird,c2的类型就是 Sky
条件类型的分发
如果你传入的是一个联合类型,它会进行条件的分发。
// 只有裸的类型(naked type)才可以分发,依次代入,最后取结果的联合类型type Condition<T> = T extends Fish ? Water : Sky;// 先用 Fish 代入泛型T,再用 Bird 代入泛型T,最后取结果的联合类型。// (Fish extends Fish ? Water : Sky) | (Bird extends Fish ? Water : Sky)// => Condition<Fish|Bird> = Water|Skylet c3:Condition<Fish|Bird> = {name2: 'jack'} //oklet c4:Condition<Fish|Bird> = {name4: 'jack'} //oklet c5:Condition<Fish|Bird> = {name2: 'jack', name4: 'jack'} //ok
条件类型有一个特性,就是【分布式有条件类型】,但是分布式有条件类型是有前提的,即条件类型里待检查的类型必须是 naked type parameter
// T[] [T] {t: T} 等都不是裸的类型,只能整体代入。type Condition<T> = T[] extends Fish[] ? Water : Sky;// (Fish|Bird)[] 不是 Fish[] 的子类型,所以结果是 Sky 类型// => Condition<Fish|Bird> = Skylet con1:Condition<Fish|Bird> = {name2: 'jack'}; //errorlet con2:Condition<Fish|Bird> = {name4: 'jack'}; //ok
示例1:如果用户传递了name,就必须传递age
interface Person {name:string, age:number}interface Person2 {age?:number, sex:string}type Pe<T> = T extends {name:string} ? Person : Person2;let p1:Pe<Person> = {name: 'jack', age: 18};let p2:Pe<Person2> = {age:18, sex: 'male'};
示例2:找出T中不包含U的部分
// 找出T中不包含U的部分type Diff<T, U> = T extends U ? never : T;type R1 = Diff<'a'|'b'|'c', 'a'|'b'>;// => 'a' 是否是 'a'|'b' 的子类型,是 => never// => 'b' 是否是 'a'|'b' 的子类型,是 => never// => 'c' 是否是 'a'|'b' 的子类型,否 => 'c'// => type R1 = never | never | 'c';// => type R1 = 'c';let r1:R1 = 'c'; //ok// 找出T中包含U的部分type Filter<T, U> = T extends U ? T : never;type R2 = Filter<'a'|'b'|'c', 'a'|'b'>;// => type R2 = 'a' | 'b';let r2:R2 = 'a'; //oklet r3:R2 = 'b'; //ok
内置条件类型
- TS 在内置了一些常用的条件类型,可以在 lib.es5.d.ts 中查看:
- utility-types
Exclude 排除
从 T 可分配给的类型中排除 属于U子类型的类型
// type MyExclude<T, U> = Exclude<T, U>; //内置类型方法type MyExclude<T, U> = T extends U ? never : T;type R = MyExclude<string|number|boolean, boolean>;// => string 不是 U(boolean) 的子类型,返回 string// => number 不是 U 的子类型,返回 number// => boolean 是 U 的子类型,返回 never// => R = string | number | never// => R = string | number
Extract 提取
从 T 可分配给的类型中提取 属于U子类型的类型
// type MyExtract<T, U> = Extract<T, U>; //内置类型方法type MyExtract<T, U> = T extends U ? T : never;type R = MyExtract<string|number|boolean, string|number>;// => string 是 U(string|number) 的子类型,返回 string// => number 是 U 的子类型,返回 number// => boolean 不是 U 的子类型,返回 never// => R = string | number
NonNullable 排除null、undefined
从 T 中排除 null 和 undefined
// type MyNonNullable<T> = NonNullable<T>; //内置类型方法type MyNonNullable<T> = T extends (null | undefined) ? never : T;type R = MyNonNullable<string|number|null|undefined>// => R = string | number
ReturnType 推断函数返回值类型
- infer最早出现在此 PR 中,表示在
extends条件语句中待推断的类型变量。 - infer 的意思是声明一个 待推断 的类型变量R,R根据 infer 所在的位置推断类型
- 仅条件类型的 “extends”子句中才允许 “infer”声明。
- 获取函数类型的返回值类型。
```typescript
function getUser(){
return {name: ‘jack’, age: 10};
}
type R1 = ReturnType
; //内置类型方法
// T 要满足是一个函数类型,所以为 泛型T 也加上约束条件: T extends (…args:any[]) => any
// infer 仅条件类型的 extends 子句中才允许 infer 声明,否则无法使用
// 这里 infer 处于函数的返回位置,R 就指 T返回值 的类型
type Fn = (…args:any[]) => any;
type MyReturnType
type R2 = MyReturnType
<a name="oMmNa"></a>## Parameters 推断函数参数类型- Constructs a tuple type of the types of the parameters of a function type T- 以元祖形式返回- [Parameters](http://www.typescriptlang.org/docs/handbook/utility-types.html#parameterst)```typescripttype Fn = (...args:any[]) => any;type MyParameters<T extends Fn> = T extends (...args: infer P) => any ? P : never;type R2 = MyParameters<typeof getUser>;// => R2 = [x:string, y:number]
InstanceType、ConstructorParameters
InstanceType获取 构造函数 new实例函数 的返回值类型(即实例类型)ConstructorParameters获取构造函数 new实例函数 的参数类型(即构造函数的参数类型)- InstanceType
```typescript
class Person {
constructor(name:string){}
}
type R1 = ConstructorParameters
; //内置类型方法 type R3 = InstanceType ; //内置类型方法
type Con = new (…args:any[]) => any; //声明 构造函数 类型
type MyConstructorParameters
type R2 = MyConstructorParameters
<a name="iTASL"></a>## infer + 分布式- `infer` 关键字就是声明一个类型变量,当类型系统给足条件的时候类型就会被推断出来- [distributive-conditional-types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types)- 「Distributive conditional types」主要用于拆分 extends 左边部分的联合类型- 「Distributive conditional types」是由「naked type parameter」构成的条件类型。而「naked type parameter」表示没有被 Wrapped 的类型(如:Array、[T]、Promise 等都是不是「naked type parameter」)。「Distributive conditional types」主要用于拆分 extends 左边部分的联合类型,举个例子:在条件类型 T extends U ? X : Y 中,当 T 是 A | B 时,会拆分成 A extends U ? X : Y | B extends U ? X : Y;- 利用在逆变位置上,同一类型变量的多个候选类型将会被推断为[交叉类型的特性](https://github.com/Microsoft/TypeScript/pull/21496)```typescript// 获取一个对象中 name 属性的类型// “T” 是否是 “{name: infer X}” 的子类型。是,返回类型X;否,返回类型nevertype A<T> = T extends {name: infer X} ? X : never;// infer 意思是声明一个 待推断 的类型变量X,X根据infer所在的位置推断类型。// 这里 infer 处于对象name属性位置,X就指对象name属性的类型。let obj = {name: 'jack'};type Aname1 = A<typeof obj>;// => type Aname1 = string;type Aname2 = A<{name: number}>;// => type Aname2 = number;type Aname3 = A<string>;// => Aname3 = never;// 为泛型 T 添加约束,如果不是 "{[key:string]:any}" 的子类型,就报错type B<T extends {[key:string]:any}> = T extends {name: infer X} ? X : never;type Bname1 = B<string>; //error,类型 “string” 不满足约束 “{[key:string]:any}”type Bname2 = B<{}>; //ok
tuple 转 union
type ElementOf<T> = T extends Array<infer E> ? E : never;type Ttuple = [string, number];type TupleToUnion = ElementOf<Ttuple>;// => type TupleToUnion = string|number
联合类型 转 交叉类型
//union 转 intersection 的操作多用于 mixin 中//https://github.com/Microsoft/TypeScript/issues/27907// string|number => string & numbertype T1 = {name:string};type T2 = {age:number};type ToIntersection<T> = T extends {a: (x:infer U) => void,b: (x:infer U) => void,} ? U : never;type T3 = ToIntersection<{a: (x:T1) => void,b: (x:T2) => void,}>// => U 必须同时满足 是 T1、T2 的子类型(参数是逆变的)// => type T3 = T1 & T2;
内置工具类型
- TS 中内置了一些工具类型来帮助我们更好地使用类型系统
- utility-types
- TypeScript中增加了对映射类型修饰符的控制
- 具体而言,一个
readonly或?修饰符在一个映射类型里可以用前缀+或-来表示这个修饰符应该被添加或移除
Partial 可选
- 将传入的属性由非可选变为可选。默认只是最外层变为可选。
- 修饰符
+?、?,其中+号可以去掉。 ```typescript interface ICompany{name:string, address:string} interface IPerson{name:string, age:number, company:ICompany}
type R1 = Partial
// 先通过 “keyof T” 拿到 T 所有key的集合;再用 in 遍历 所有的key
type MyPartial
// 但是上面的只是单层,怎么办呢? 类型深度递归
type MyDeepPartial
<a name="a6Tp9"></a>## Required 必选- 将传入的属性中的可选项变为必选项,默认只是最外层变为必选。- 修饰符 `-?````typescriptinterface ICompany{name?:string, address?:string}interface IPerson{name?:string, age?:number, company?:ICompany}type R1 = Required<IPerson>; //内置类型方法type MyRequired<T> = {[K in keyof T]-?: T[K]}type R2 = MyRequired<IPerson>// => R2 = {// name:string,// age:number,// company:ICompany,// }
Readonly 只读
为传入的属性每一项都加上 readonly 修饰符来实现
interface ICompany{name?:string, address?:string}interface IPerson{name?:string, age?:number, company?:ICompany}type R1 = Readonly<IPerson>; //内置类型方法type MyReadonly<T> = {readonly [K in keyof T]: T[K]}type R2 = MyReadonly<IPerson>// => R2 = {// readonly name?: string|undefined,// readonly age?: number|undefined,// readonly company?: ICompany|undefined,// }// 只把name属性变为只读type ReadonlyNamePerson = Person & {readonly name:string};
Pick 挑选属性
从对象属性中,提取指定的属性。Pick<object, union>
Pick 从对象里选属性; extract 从类型中选择类型
interface IPerson{name:string, age:number, sex: string};type R1 = Pick<IPerson, 'name'|'age'>; //内置类型方法type MyPick<T, U extends keyof T> = {[K in U]: T[K]}type R2 = MyPick<IPerson, 'name'|'age'>// => R2 = {name:string, age:number}// 提取指定属性并组成新对象function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {const result: any = {};keys.map(key => {result[key] = obj[key];});return result}let p3:IPerson = {name: 'jack', age: 10, sex: 'male'};let result:Pick<IPerson, 'name'|'age'>= pick<IPerson, 'name'|'age'>(p3, ['name', 'age']);console.log(result); //{ name: 'zhufeng', age: 10 }
Omit 忽略属性
Omit 从对象属性中,忽略指定属性,提取剩下的。Omit<object, union>
interface IPerson{name:string, age:number, sex: string};type R1 = Omit<IPerson, 'name'|'age'>; //内置类型方法type MyOmit<T, U extends keyof T> = Pick<T, Exclude<keyof T, U>>type R2 = MyOmit<IPerson, 'name'|'age'>// => R2 = {sex:number}// 示例1:只想要里面的name属性变为必选interface IPerson2{name?:string, age?:number, sex: string};type R3 = Omit<IPerson2, 'name'> & {name:string};let r3:R3 = {name: 'jack', sex: 'male'}; //oklet r4:R3 = {sex: 'male'}; //error
Record 描述对象
- 将一个类型的所有属性都映射到另一个类型上并创造一个新的类型。
```typescript
// 对象的属性名只能是字符串、数字、symbol。所以为 泛型K 添加约束
// keyof any 等同于 string | number | symbol
type Record
= {
// [] 表示任意属性 // 如果是示例1,等同于: // [key:string]: string; // [key:number]: string;
// 如果是示例2,等同于
// x: {value:number},
// y: {value:number}
};
// 示例1:
// k的key只能是string|number类型,k的value只能是string类型
let k:Record
// 示例2:
type Point = ‘x’ | ‘y’;
type PointList = Record
<a name="HiG4J"></a>##### 示例:把一个对象映射成新的对象```typescript// 把一个对象映射成新的对象function mapObj<K extends keyof any, V, X>(obj: Record<K, V>, //老对象cb: (item:V, key:K) => X, //映射函数):Record<K, X>{let result = {} as Record<K, X>;for (let key in obj){result[key] = cb(obj[key], key);}return result;}let obj = {a: 1, b: 2};let newObj = mapObj(obj, (item, key) => {return item*2;})console.log(newObj); //{a: 2, b: 4}
自定义高级类型
Proxy 代理
let data = {name: 'jack', age: 18};type Proxy<V> = {get():V,set(value:any):void,}type Proxify<T extends object> = {[K in keyof T]: Proxy<T[K]>}
设置代理(装包)
function proxify<T extends object>(obj:T):Proxify<T>{let result = {} as Proxify<T>;for (let key in obj){let value = obj[key];Object.defineProperty(result, key, {enumerable: true,value: {get: () => value,set: (newValue:any) => {value = newValue;},},})}return result;}let proxifyData = proxify(data); //包装后的对象console.log(proxifyData); //{name: {get(){}, set(){}}, age: {get(){}, set(){}}}console.log(proxifyData.name.get()); //jackproxifyData.name.set('lucky');console.log(proxifyData.name.get()); //lucky
取消代理(拆包)
function unProxify<T extends object>(obj: Proxify<T>):T{let result = {} as T;for (let key in obj){result[key] = obj[key].get();}return result;}let originData = unProxify(proxifyData); //源对象,即拆包后的对象console.log(originData); //{name: 'lucky', age: 18}
Diff 差集
从O1的属性中,提取二者不同的属性。Diff<O1, O2>
interface Person1 {name:string, age:number, address:string};interface Person2 {address:string, sex:boolean};type Diff<T extends object, U extends object> = Omit<T, keyof U>;type R1 = Diff<Person1, Person2>;// => R1 = {name:string, age:number}
Inter 交集
从O1的属性中,提取二者相同的属性。Inter<O1, O2>
interface Person1 {name:string, age:number, address:string};interface Person2 {address:string, sex:boolean};interface Person3 {address:boolean, sex:boolean};// 交叉类型,交叉后应是两者的子类型type R1 = Person1 & Person2; // => R1 = {name:string, age:number, address:string, sex:boolean}// 交叉属性,取两个对象相同的地方type Inter<T extends object, U extends object > = Pick<T, Extract<keyof T, keyof U>>;type Inter2<T extends object, U extends object> = Omit<T, Exclude<keyof T, keyof U>>;type R2 = Inter<Person1, Person2>; // => R2 = {address:string}type R3 = Inter2<Person1, Person2>; // => R3 = {address:string}// 如果同一属性,类型不同,取第一个参数的类型。type R4 = Inter<Person1, Person3>; // => R2 = {address:string}
Overwrite 覆盖
- 针对O1,用O2的属性覆盖O1的属性。
Overwrite(O1, O2) - mapped-types ```typescript interface Person1 {name:string, age:number, }; interface Person2 {age:string, address:string};
type Diff
type R1 = Overwrite
<a name="6lZHm"></a>## Merge 合并将两个对象合并,一般都是以后者为准。`merge<O1, O2>````typescriptinterface Person1 {name:string, age:number, };interface Person2 {age:string, address:string};// 展开类型,方便提示type Compute<T extends object> = T extends Function ? T : {[K in keyof T]: T[K]}type Merge<T extends object, U extends object> = Omit<T, keyof U> & U;type R1 = Merge<Person1, Person2>; //但是这样的提示不易读type R1Compute = Compute<R1>; //格式化后,鼠标移上去,就很已读了// => R1 = {name:string, age:string, address: string}let r1:R1 = {name: '', age: '', address: ''}; //ok
Mutable 移除只读
- 将 T 的所有属性的
readonly移除type Mutable<T> = {-readonly [P in keyof T]: T[P]}type A = {readonly name:string;readonly age:number;}type R = Mutable<A>;// => type R = {name:string; age:number}
