10. 类型变换

160 阅读12分钟

10. 类型变换

10.1 类型推断

  • TypeScript 能根据一些简单的规则推断变量的类型

10.1.1 从右向左

  • 变量的类型可以由定义推断

  • 这是一个从右向左流动类型的示例


let foo = 1; // foo 是 'number'

let bar = 'zhufeng'; // bar 是 'string'

//foo = bar; // Error: 不能将 'string' 赋值给 `number`

```]

### 10.1.2 底部流出

- 返回类型能被 return 语句推断

```javascript

function add(a: number, b: number) {

return a + b;

}

let c = add(1,2);

10.1.3 从左向右

  • 函数参数类型/返回值类型也能通过赋值来推断

type Sum3 = (a: number, b: number) => number;

let sum33: Sum3 = (a, b) => {

a='zhufeng'; // Type 'string' is not assignable to type 'number'

return a + b;

};

10.1.4 结构化

  • 推断规则也适用于结构化的存在(对象字面量)

const person = {

name: 'zhufeng',

age: 11

};

let name =person.name;

let age =person.age;

age = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型

10.1.5 解构

  • 推断规则也适用于解构

const person = {

name: 'zhufeng',

age: 11

};

let { name,age } = person;

age = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型

//数组也一样

const numbers = [1, 2, 3];

numbers[0] = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型

  • DefaultProps

interface DefaultProps{

name?:string;

age?:number;

}

let defaultProps: DefaultProps = {

name:'zhufeng',

age:10

}

let props = {

...defaultProps,

home:'北京'

}

type Props = typeof props;

10.1.6 小心使用返回值

  • 尽管 TypeScript 一般情况下能推断函数的返回值,但是它可能并不是你想要的

function addOne(a:any){

return a+1;

}

function sum3(a:number,b:number){

return a+addOne(b) as number;

}

let j = addOne(1); // any

let k = sum3(1,2); //number+any = any

10.1 交叉类型

  • 交叉类型(Intersection Types)是将多个类型合并为一个类型

  • 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性


//TypeScript 交叉类型是将多个类型合并为一个类型

//这让我们可以把现有的多种类型叠加到一起成为一种类型

//它包含了所需的所有类型的特性

export {}

//接口的交叉

interface Bird {

name: string,

fly(): void

}

interface Person {

name: string,

talk(): void

}

type BirdPerson = Bird & Person;

let p: BirdPerson = { name: 'zhufeng', fly() { }, talk() { } };

p.fly;

p.name

p.talk;


interface X {

a: string;

b: string;

}

interface Y {

a: number;

c: string

}

type XY = X & Y;

type YX = Y & X;

//c = string & number

//let p1: XY={a:'',b:'',c:''};

联合类型的交叉类型


type Ta = string | number;

type Tb = number | boolean;

type Tc = Ta & Tb;

mixin混入模式可以让你从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能


interface AnyObject {

[prop: string]: any;

}

function mixin<T extends AnyObject, U extends AnyObject>(one: T,two: U): T & U {

const result = <T & U>{};

for (let key in one) {

(<T>result)[key] = one[key];

}

for (let key in two) {

(<U>result)[key] = two[key];

}

return result;

}

const x = mixin({ name: "zhufeng" }, { age: 11 });

console.log(x.name, x.age);

10.2 typeof

  • 可以获取一个变量的类型

//先定义类型,再定义变量

type People = {

name:string,

age:number,

gender:string

}

let p1:People = {

name:'zhufeng',

age:10,

gender:'male'

}


let p1 = {

name:'zhufeng',

age:10,

gender:'male'

}

type People = typeof p1;

function getName(p:People):string{

return p.name;

}

getName(p1);

10.3 索引访问操作符

可以通过[]获取一个类型的子类型


interface Person{

name:string;

age:number;

job:{

name:string

};

interests:{name:string,level:number}[]

}

let FrontEndJob:Person['job'] = {

name:'前端工程师'

}

let interestLevel:Person['interests'][0]['level'] = 2;

10.4 keyof

  • 索引类型查询操作符

interface Person{

name:string;

age:number;

gender:'male'|'female';

}

//type PersonKey = 'name'|'age'|'gender';

type PersonKey = keyof Person;

function getValueByKey(p:Person,key:PersonKey){

return p[key];

}

let val = getValueByKey({name:'zhufeng',age:10,gender:'male'},'name');

console.log(val);

10.5 映射类型

  • 在定义的时候用in操作符去批量定义类型中的属性

interface Person{

name:string;

age:number;

gender:'male'|'female';

}

//批量把一个接口中的属性都变成可选的

type PartPerson = {

[Key in keyof Person]?:Person[Key]

}

let p1:PartPerson={};

//也可以使用泛型

type Part<T> = {

[key in keyof T]?:T[key]

}

let p2:Part<Person>={};

  • 通过key的数组获取值的数组

function pick<T, K extends keyof T>(o: T, names: K[]): T[K][] {

return names.map((n) => o[n]);

}

let user = { id: 1, name: 'zhufeng' };

type User = typeof user;

const res = pick<User, keyof User>(user, ["id", "name"]);

console.log(res);

10.6 条件类型

  • 在定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活

10.6.1 定义条件类型


interface Fish {

name: string

}

interface Water {

name: string

}

interface Bird {

name: string

}

interface Sky {

name: string

}

//若 T 能够赋值给 Fish,那么类型是 Water,否则为 Sky

type Condition<T> = T extends Fish ? Water : Sky;

let condition: Condition<Fish> = { name: '水' };

10.6.2 条件类型的分发


interface Fish {

fish: string

}

interface Water {

water: string

}

interface Bird {

bird: string

}

interface Sky {

sky: string

}

//naked type

type Condition<T> = T extends Fish ? Water : Sky;

//(Fish extends Fish ? Water : Sky) | (Bird extends Fish ? Water : Sky)

// Water|Sky

let condition1: Condition<Fish | Bird> = { water: '水' };

let condition2: Condition<Fish | Bird> = { sky: '天空' };

  • 只有类型系统中给出 充足的条件 之后,它才会根据条件推断出类型结果,如果判断条件不足,则会得到第三种结果,即 推迟 条件判断,等待充足条件

interface Foo {

propA: boolean;

propB: boolean;

}

declare function f<T>(x: T): T extends Foo ? string : number;

function foo<U>(x: U) {

// 因为 ”x“ 未知,因此判断条件不足,不能确定条件分支,推迟条件判断直到 ”x“ 明确,

// 推迟过程中,”a“ 的类型为分支条件类型组成的联合类型,

// string | number

let a = f(x);

// 这么做是完全可以的

let b: string | number = a;

}

  • 条件类型有一个特性,就是分布式有条件类型,但是分布式有条件类型是有前提的,条件类型里待检查的类型必须是naked type parameter(裸类型参数)

先看什么类型是裸类型

裸类型是指类型参数没有被包装在其他类型里,比如没有被数组、元组、函数、Promise等等包裹,简而言之裸类型就是未经过任何其他类型修饰或包装的类型。


// 裸类型参数,没有被任何其他类型包裹,即T

type NakedType<T> = T extends boolean ? "YES" : "NO"

// 类型参数被包裹的在元组内,即[T]

type WrappedType<T> = [T] extends [boolean] ? "YES" : "NO";

//none naked type

type Condition<T> = [T] extends [Fish] ? Water : Sky;

分布式如何理解

分布式条件类型在实例化时会自动分发成联合类型

什么意思呢?

例如,T extends U ? X : Y使用类型参数A | B | C 实例化 T 解析为 (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)

结合 乘法分配律 理解一下!

接下来结合具体实例我们来看一下分布式条件类型 与 不含有分布式特性的条件类型


// 含有分布式特性的,待检查类型必须为”裸类型“

type Distributed = NakedType<number | boolean> // = NakedType<number> | NakedType<boolean> = "NO" | "YES"(结合一下乘法分配律便于理解与记忆哦~)

// 不含有分布式特性的,待检查的类型为包装或修饰过的类型

type NotDistributed = WrappedType<number | boolean > // "NO"

  • 找出T类型中不包含U的部分

type Diff<T,U> = T extends U?never:T;

type R = Diff<'a'|'b'|'c'|'d' , 'a'|'b'|'c'>;

type R2 = 'd';

  • 找出T类型中包含U的部分

type Filter<T,U> = T extends U?T:never;

type R3 = Filter<'a' | 'b' | 'c' | 'd', 'a' | 'b' | 'c'>;

type R33 = Filter<string | number | boolean, number>;

10.6.3 内置条件类型

10.6.3.1 Exclude

  • 从 T 可分配给的类型中排除 U

type Exclude<T, U> = T extends U ? never : T;

type E = Exclude<string|number,string>;

let e:E = 10;

10.6.3.2 Extract

  • 从 T 可分配的类型中提取 U

type Extract<T, U> = T extends U ? T : never;

type E = Extract<string|number,string>;

let e:E = '1';

10.6.3.3 NonNullable

  • 从 T 中排除 null 和 undefined

type NonNullable<T> = T extends null | undefined ? never : T;

type E = NonNullable<string|number|null|undefined>;

let e:E = null;

10.6.3.4 ReturnType

  • infer最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量

  • 获取函数类型的返回类型


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

function getUser(a:string,b:number){

return {name:'zhufeng',age:10};

}

let t = getUser('1',2);

type GetUserType = typeof getUser;

type ReturnUser = ReturnType<GetUserType>;

let u: ReturnUser = {

name:'zf',

age:10

}

10.6.3.5 Parameters

  • Constructs a tuple type of the types of the parameters of a function type T

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

type X1 = {}

type ParamsType = Parameters<GetUserType>; //[a: string, b: number]

type T0 = Parameters<() => string>; // []

type T11 = Parameters<(s: string) => void>; // [s:string]

type T22 = Parameters<(<T>(arg: T) => T)>; // [arg:unknown]

10.6.3.6 InstanceType

  • 获取构造函数类型的实例类型

type Constructor = new (...args: any[]) => any;

type ConstructorParameters<T extends Constructor> = T extends new (...args: infer P) => any ? P : never;

type InstanceType<T extends Constructor> = T extends new (...args: any[]) => infer R ? R : any;

class Person {

name: string;

constructor(name: string) {

this.name = name;

}

getName() { console.log(this.name) }

}

//构造函数参数

type constructorParameters = ConstructorParameters<typeof Person>;

let params: constructorParameters = ['zhufeng']

//实例类型

type Instance = InstanceType<typeof Person>;

let instance: Instance = { name: 'zhufeng', getName() { } };

10.6.3.7 infer+分布式

  • 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;

  • 利用在逆变位置上,同一类型变量的多个候选类型将会被推断为交叉类型的特性

  • tuple转union


type ElementOf<T> = T extends Array<infer E> ? E : never;

type TTuple = [string, number];

type ToUnion = ElementOf<TTuple>; // string | number

  • 联合类型转成交叉类 union 转 intersection

//联合类型(Union Types)表示取值可以为多种类型中的一种

//交叉类型(Intersection Types)表示将多个类型合并为一个类型

//联合类型转交叉类型

//union 转 intersection

//union 转 intersection 的操作多用于 mixin 中

//https://github.com/Microsoft/TypeScript/issues/27907

type T1 = { name: string };

type T2 = { age: number };

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

type T3 = UnionToIntersection<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2

10.7 内置工具类型

  • TS 中内置了一些工具类型来帮助我们更好地使用类型系统utility-types

  • TypeScript中增加了对映射类型修饰符的控制,具体而言,一个 readonly 或 ? 修饰符在一个映射类型里可以用前缀 + 或-来表示这个修饰符应该被添加或移除

| 符号 | 含义 |

| --- | --- |

| +? | 变为可选|

| -? | 变为必选 |

10.7.1 Partial

  • Partial 可以将传入的属性由非可选变为可选,具体使用如下:

type Partial<T> = { [P in keyof T]?: T[P] };

interface A {

a1: string;

a2: number;

a3: boolean;

}

type aPartial = Partial<A>;

const a: aPartial = {}; // 不会报错

10.7.2 类型递归


interface Company{

id:number;

name:string;

}

interface Person{

id:number;

name:string;

company: Company

}

type DeepPartial<T>= {

[U in keyof T]+?: T[U] extends object ? DeepPartial<T[U]>:T[U]

}

type PartialPerson = DeepPartial<Person>;

let p: PartialPerson = {

id:1,

name:'zhufeng',

company:{}

}

10.7.3 Required

  • Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。

interface Person{

name:string;

age?:number;

}

type Required<T> = {

[P in keyof T]-?: T[P];

};

type RequiredPerson = Required<Person>;

let p: RequiredPerson={

name:'zhufeng',

age:11

}

10.7.4 Readonly

  • Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。

interface Person {

name: string;

age: number;

}

type Readonly<T> = {

readonly [P in keyof T]: T[P];

};

type ReadOnlyNamePerson = Readonly<Person>;

type ReadOnlyNamePerson2 = Person&{

readonly name:string;

};

let p: ReadOnlyNamePerson = {

name: 'zhufeng',

age: 11

}

//p.name = 'jiagou';

//p.age = 11;

10.7.5 Pick

  • Pick 能够帮助我们从传入的属性中摘取某一项返回

interface Person {

name: string;

age: number;

gender:number

}

let person:Person = {name:'zhufeng',age:11,gender:1};

type KeyOfPerson = keyof Person;// 'name'|'age'|'gender'

type Pick<T, K extends keyof T> = {

[P in K]: T[P];

};

type PickPerson = Pick<Person,'name'|'age'>;

//Extract

type Extract<T, U> = T extends U ? T : never;

//string|number

//有条件类型分发

type E = Extract<string | number|boolean, string|number>;

let e: E = '1';

10.7.6 Record

  • Record 是 TypeScript 的一个高级类型

  • 他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型


type KeyOfAny = keyof any; //string | number | symbol 联合类型

type Record<K extends keyof any, T> = {

[P in K]: T;

//[]表示任意属性

//[key: string]: string,

//[key: number]: string

};

let k: Record<string|number,string>= {name:'zhufeng',age:'11'};

type Point = 'x' | 'y';

type PointList = Record<Point, { value: number }>

const cars: PointList = {

x: { value: 10 },

y: { value: 20 }

}

function mapObject<K extends string|number,T,U>(obj:Record<K,T>,map:(x:T)=>U){

let result: Record<K, U> = <Record<K, U>>{};

for(const key in obj){

result[key]=map(obj[key]);

}

return result;

}

let obj = {count1:1,count2:2,3:3};

let map = (x: number):string => x * 2 + '';

let newObj = mapObject<string | number, number, string>(obj, map);//{'3': '6',count1:2,count2:4}

10.8 自定义高级类型

10.8.1 Proxy


type Proxy<T>= {

get():T,

set(value:T):void;

}

type Proxify<T>= {

[P in keyof T]:Proxy<T[P]>

}

function proxify<T>(obj:T):Proxify<T>{

let result = <Proxify<T>>{};

for(const key in obj){

Object.defineProperty(result, key, {

get: () => {

//console.log('get ', key);

return obj[key];

},

set: (value) => {

//console.log('set ', key, value);

obj[key] = value;

},

enumerable: true

});

}

return result;

}

interface Props{

name:string;

age: string

}

let props: Props = {

name:'zhufeng',

age:'k'

};

let proxyProps:any = proxify<Props>(props);

console.log(proxyProps);

console.log(proxyProps.name);

proxyProps.name = 'jiagou';

proxyProps.age = 'k';

console.log(proxyProps.name);

function unProxify<T>(t:Proxify<T>):T{

let result:any = {} as T;

for(const k in t){

result[k]= t[k];

}

return result;

}

let originalProps = unProxify<Props>(proxyProps);

console.log(originalProps);

10.8.2 SetDifference

  • SetDifference (same as Exclude)

**

* 差集 A-B = Exclude

*/

export type SetDifference<A,B>= A extends B ?never:A;

type A = string|number;

type B = number|boolean;

type AB = SetDifference<A,B>;

10.8.3 Omit

  • Exclude 的作用是从 T 中排除出可分配给 U的元素.

  • Omit<T, K>的作用是忽略T中的某些属性

  • Omit = Exclude + Pick


//Omit=Exclude+Pick

//keyof T = name|age|visible;

//K = age

//SetDifference name|visible

//{name:string,visible:boolean}

type Omit<T,K extends keyof any>=Pick<T,SetDifference<keyof T,K>>;

type Props = {name:string,age:number,visible:boolean};

type OmitAgeProps = Omit<Props,'age'>;

// {name:string,visible:boolean};

10.8.4 Diff

  • From T remove properties that exist in U

/**

* Diff

* @desc From `T` remove properties that exist in `U`

* @example

* type Props = { name: string; age: number; visible: boolean };

* type DefaultProps = { age: number };

*

* // Expect: { name: string; visible: boolean; }

* type DiffProps = Diff<Props, DefaultProps>;

*/

export type Diff<T extends object, U extends object> = Pick<

T,

SetDifference<keyof T, keyof U>

>;

10.8.5 Intersection

  • From T pick properties that exist in U

//InterSection 交叉属性

type InterSection<T extends object, U extends object>=Pick<T,

Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;

type Props = { name: string, age: number, visible: boolean };

type DefaultProps = { age: number };

// Expect: { age: number; }

type DuplicateProps = InterSection<Props, DefaultProps>;

10.8.6 Overwrite

  • Overwrite<T, U>顾名思义,是用U的属性覆盖T的相同属性.

//Overwrite

type OldProps = { name: string, age: number, visible: boolean };

type NewProps = {age:string,other:string};

//{ name: string,visible: boolean ,age:string}

type Diff<T extends object, U extends object> = Pick<T, SetDifference<keyof T, keyof U>>;

type InterSection<T extends object, U extends object> = Pick<T,

Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;

type Overwrite<

T extends object,

U extends object,

// {name: string,visible: boolean } & {age:string} {name: string,visible: boolean,age:string }

I = Diff<T,U> & InterSection<U,T>

>=Pick<I,keyof I>

type ReplacedProps = Overwrite<OldProps, NewProps>;

let p: ReplacedProps={name:'',age:'',visible:true}

10.8.7 Merge

  • Merge<O1, O2>的作用是将两个对象的属性合并:

  • Merge<O1, O2> = Compute + Omit<U, T>


type O1 ={

id:number;

name:string

}

type O2 = {

id:number;

age:number;

}

type Compute<A extends any>=A extends Function?A:{[K in keyof A]:A[K]}

type R1 = Compute<string>;

type R11 = Compute<{x:'x',y:'y'}>;//

type R12=Omit<O2,keyof O1>

type R13=O1&R12;

type R14 = Compute<O1>

type Omit<T, K extends keyof any> = Pick<T, SetDifference<keyof T, K>>;

type Merge<O1 extends object, O2 extends object> = Compute<

O1&Omit<O2,keyof O1>

>

type R2 = Merge<O1,O2>;

let r2: R2 = {

id: 1,

name: '1',

age: 1

}

10.8.8 Mutable

  • 将 T 的所有属性的 readonly 移除

type O1 ={

id:number;

readonly name:string

}

let o1:O1={

id:1,

name:'aa'

}

//o1.name='bb' //(property) name: string Cannot assign to 'name' because it is a read-only property.

type Mutable<T> = {

-readonly [P in keyof T]: T[P]

}

type O2=Mutable<O1>

let o2:O2={

id:1,

name:'aa'

}

o2.name='bb';

10.9 面试题综合实战

  • infer 关键字就是声明一个类型变量,当类型系统给足条件的时候类型就会被推断出来

  • 问题 typescript_zh

  • 答案


interface Action<T> {

payload?: T;

type: string;

}

class EffectModule {

count = 1;

message = "hello!";

delay(input: Promise<number>): Promise<Action<string>> {

let action: Promise<Action<string>> = input.then(i => ({

payload: `hello ${i}!`,

type: 'delay'

}));

return action;

}

setMessage(action: Action<Date>): Action<number> {

let action2: Action<number> = {

payload: action.payload!.getMilliseconds(),

type: "set-message"

};

return action2;

}

}

//把 EffectModule 中的方法名取出来

type aa = keyof EffectModule

type methodsPick<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T];

//定义转换前后的方法

type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> // 转换前

type asyncMethodConnect<T, U> = (input: T) => Action<U> // 转换后

type syncMethod<T, U> = (action: Action<T>) => Action<U> // 转换前

type syncMethodConnect<T, U> = (action: T) => Action<U> // 转换后

//条件类型+推断类型

type EffectModuleMethodsConnect<T> = T extends asyncMethod<infer U, infer V>

? asyncMethodConnect<U, V>

: T extends syncMethod<infer U, infer V>

? syncMethodConnect<U, V>

: never

type EffectModuleMethods = methodsPick<EffectModule>

//映射类型

type Connect = (module: EffectModule) => {

[M in EffectModuleMethods]: EffectModuleMethodsConnect<EffectModule[M]>

}

type Connected = {

delay(input: number): Action<string>;

setMessage(action: Date): Action<number>;

};

// 修改 Connect 的类型,让 connected 的类型变成预期的类型

/**

* 1. m 取出它的所有的方法或者说函数

* 2. 按同步异步分成二组,分别进行转换

* 3.再把它们合并成一个对象

*/

const connect: Connect = (m: EffectModule): Connected => ({

delay: (input: number) => ({

type: 'delay',

payload: `hello 2`

}),

setMessage: (input: Date) => ({

type: "set-message",

payload: input.getMilliseconds()

})

});

export const connected: Connected = connect(new EffectModule());