当行动变得无利可图时,收集信息;当信息变得无利可图时,睡觉; ——《黑暗的左手》

接口智能提示

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Seal {
name: string
}
interface API {
'/user': {name: string, age: number},
'/seal': {
seal: Seal[]
}
}
const api = <URL extends keyof API>(url: URL): Promise<API[URL]> => {
return fetch(url).then(res => res.json())
}
api('/user').then(res => res.seal) // 类型“{ name: string; age: number; }”上不存在属性“seal”

高级特性的实际场景应用

泛型使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Mode = 'base' | 'same' | 'other'
type ModeObject = {
base: string,
same: string,
other: string
}
function selectMode<K extends Mode>(type: K): K {
console.log('type', type);
return type
}
var t1 = selectMode('111') // error 类型“"111"”的参数不能赋给类型“Mode”的参数
function selectModeObject<K extends keyof ModeObject>(type: K): K {
return type
}
selectModeObject('base')

多泛型定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Generic<T, U, K> {
name: T,
age: U,
sex: K
}
function showType(args: Generic<string, number, symbol>) {
console.log('args', args);
}
showType({
name: 'cpp',
age: 28,
sex: Symbol('cpp')
})

泛型推断 infer

infer的中文是“推断”的意思,一般是搭配上面的泛型条件语句使用的,所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构。举个例子

1
type Foo<T> = T extends {t: infer Test} ? Test: string

首选看extends后面的内容,{t: infer Test}可以看成是一个包含t属性的类型定义,这个t属性的value类型通过infer进行推断后会赋值给Test类型,如果泛型实际参数符合{t: infer Test}的定义那么返回的就是Test类型,否则默认给缺省的string类型。

1
2
3
4
type Foo<T> = T extends {t: infer Test} ? Test: string
type One = Foo<number> // string,因为number不是一个包含t的对象类型
type Two = Foo<{t: boolean}> // boolean,因为泛型参数匹配上了,使用了infer对应的type
type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配

infer用来对满足的泛型类型进行子类型的抽取,有很多高级的泛型工具也巧妙的使用了这个方法。

EXtract / Exclude

我们可能想要用在函数里面使用name属性,并透传剩下的属性:

1
2
3
4
5
6
7
8
9
type ExtractName = {
name: string
}

function removeName(props) {
const {name, ...rest} = props;
// do something with name...
return rest:
}

现在给removeName函数加入类型定义

1
2
3
4
5
6
7
8
9
10
11
12
13
type ExtractName = {
name: string
}
export function removeName<Props extends ExtractName>(props: Props): Pick<Props, Exclude<keyof
Props, keyof ExtractName>> {
const {name, ...rest} = props
return rest
}
const p1 = {
name: 'cpp',
age: 30
}
removeName(p1)

上面的例子做了很多事情,它先是继承了Props来包含name属性。然后抽取了name属性,并返回剩下的属性。为了告诉TypeScript函数返回类型的结构,我们移除了ExtractName中的属性(这个例子中的name)。这些工作都是 Pick<Props, Exclude<keyof Props, keyof ExtractName>> 实现的。

重写上面的定义:

1
2
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
type Diff<T, K> = Omit<T, keyof K>;

用Diff函数重写removeName函数:

1
2
3
4
5
6
7
8
9
10
11
type Diff<T, K> = Omit<T, keyof K>

export function removeName2<Props extends ExtractName>(props: Props): Diff<Props, ExtractName> {
const {name, ...rest} = props
return rest
}
const p2 = {
name: 'cpp',
age: 30
}
removeName2(p2)

总结来说,ExtractExclude就是这样

1
2
3
type ExtractType<T, U> = T extends U ? T : never // 此时返回的T,是满足原来的T中包含U的部分,可以理解为T和U的交集

type ExcludeType<T, U> = T extends U ? never : T

真正的实际用处都是 返回一个类型的联合子类型,也就是通常是这样

1
2
3
4
5
6
7
8
9
type ExtractUser1 = Extract<'a' | 'b' | 'c', 'a' | 'b'> // 'a' | 'b'
const test: ExtractUser1 = 'a' // or 'b'

interface OtherUser {
name: string,
sex: string
}
type ExcludeUser1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
type ExcludeUser2 = Exclude<keyof User, keyof OtherUser> // 'id' 和 ‘age’

Records

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Readonly, Partial和 Pick是同态的,但 Record不是。因为 Record并不需要输入类型来拷贝属性,所以它不属于同态:
const personList: Records<number, UserI> = {
0: {
name: 'cc',
age: 30
},
1: {
name: 'ccc',
age: 30
}
}
type MockRecords<K extends keyof any, T> = {
[P in K]: T
}
type MockPick<T, K extends keyof T> = {
[P in K]: T[P]
}
type MockReadonly<T> = {
readonly [P in keyof T]: T[P]
}
type MockParital<T> = {
[P in keyof T]?: T[P]
}

高级类型模拟(重点,要考的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type MockRequired<T> = {
[P in keyof T]-?: T[P]
}

type MockRecords<K extends keyof any, T> = {
[P in K]: T
}
type MockPick<T, K extends keyof T> = {
[P in K]: T[P]
}
type MockReadonly<T> = {
readonly [P in keyof T]: T[P]
}
type MockParital<T> = {
[P in keyof T]?: T[P]
}

type MockOmit<T, K extends string | number | symbol> = Pick<T, Exclude<keyof T, K>>

type MockExclude<T, U> = T extends U ? never : T
type MockExtract<T, U> = T extends U ? T : never

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

函数相关的类型

  • Parameters 由函数类型的Type的参数类型类型构建一个元组类型
    1
    2
    type FillParams = Parameters<typeof Array.prototype.fill> // type FillParams = Parameters<typeof Array.prototype.fill>
    type SomeParams = Parameters<typeof Array.prototype.reduce> // type SomeParams = [callbackfn: (previousValue: unknown, currentValue: any, currentIndex: number, array: any[]) => unknown, initialValue: unknown]
    Parameters源码是
    1
    2
    // 此处使用 infer P 将参数定为待推断类型
    type Parameters<T extends (...arg: any) => any> = T extends (...args: infer P) => any ? P : never

使用案例

1
2
3
4
5
6
7
8
9
10
11
interface IPer {
name: string
}
interface IFunc {
(person: IPer, count: number): boolean
}
type PP = Parameters<IFunc> // 相当于 type PP = [person: IPer, count: number]
const person: PP[0] = {
name: 'cpp'
}
const count: PP[1] = 11
  • ReturnType 由函数类型Type的返回值类型构建一个新类型
    1
    2
    type T1 = ReturnType<() => string>
    const test: T1 = 111 // 不能将类型“number”分配给类型“string”
    ReturnType主要做的是对函数返回值类型进行构建
    1
    2
    3
    4
    5
    type F1 = (args: Generic<string, number, symbol>) => void
    const showType: F1 = (args: Generic<string, number, symbol>) => {
    console.log('args', args);
    }
    type ShowFunctionType = ReturnType<F1> // 相等于void
    RturnType源码是
    1
    type MockReturnType<T extends (...arg: any) => any> = T extends (...args: any) => infer R ? R : any;

自定义常用类型

  • PartialRecord
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface Model {
    name: string;
    id: number;
    }
    type Validator = {
    required: boolean;
    trigger: string
    }
    const validateRules: Record<keyof Model, Validator> = {
    name: {required: true, trigger: `blur`},
    id: {required: true, trigger: `blur`},
    email: {required: true, message: `...`},
    // error: Property age is missing in type...
    }
    这里出现了一个问题,validateRules 的 key 值必须和 Model 全部匹配,缺一不可,但实际上我们的表单可能只有其中的一两项,这时候我们就需要:
    1
    2
    3
    4
    5
    type PartialRecord<K extends keyof any, T> = Partial<Records<K, T>>

    const validateRuless: PartialRecord<keyof Model, Validator> = {
    name: {required: true, trigger: `blur`}
    }

参考文档