类型声明是最好的文档,而类型编程可以类型声明发挥到极致。类型编程是TS高手必备技能,掌握TS编程可以帮助我们的阅读理解框架源码、设计产出自己的类库及文档。本将深入讲解映射类型
和infer
,给出丰满的案例及TS编程题,带你由浅入深的掌握TS类型体操。
函数重载
、 交叉类型
、 泛型
、 索引类型
、 映射类型
、 infer
要提前掌握,前三种类型前面的课程已讲述,而索引类型
、 映射类型
、 infer
比较偏编程,故意放在这一章节讲解。keyof T
T的所有字面量的集合K extends keyof T
K
是 T
的所有属性字面量集合T[K]
为 T
的所有属性值字面量集合tsinterface Person {
name: string;
age: number;
}
type Key = keyof Person; // Key 等同于 'name' | 'age'
type ObjAddNullByKey<T, K extends keyof T> = {
[P in keyof T]: K extends keyof T ? (T[P] | null) : T[P];
}
var p: Person = {
name: '小花',
age: 2, // 这里不能写null
}
var p1: ObjAddNullByKey<Person, 'age'> = {
name: '小花',
age: null, // 这里可以写 null
}
Readonly
Partial
Required
NonNullable
Mutable
Nullable
DeapReadonly
ts// Person ---> PersonReadonly ---> Person
interface Person {
name: string;
}
type MyReadonly<T> = {
readonly [P in keyof T]: T[P];
}
var zs: MyReadonly<Person> = {name: 'zs'};
// zs.name = 'ls' // 这里报错不让改
// 可变的(即移除readonly)
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}
// typeof zs 取出js对象的数据类型,这里即 MyReadonly<Person>
var zs2:Mutable<typeof zs> = zs;
zs2.name = 'zs2';
// 类似的还有 Partial Require
type MyPartial<T> = {
[P in keyof T]?: T[P] | undefined
}
const ls: MyPartial<Person> = {};
type MyRequired<T> = {
[P in keyof T]-?: T[P];
}
let ww: MyRequired<typeof ls> = {
name: 'ww' // 这里不能注释,必须写name
}
// NonNullable
type NullName = string | null | undefined;
let zl: NullName = null;
let sunqi: NullName & {} = 'sunq' // sunqi: string
let sunqi2: NonNullable<NullName> = 'sunq' // sunqi: string
/**
* type NonNullable<T> = T & {}
* T & {} 如何理解?
* 如果是联合类型 比如 `string | null | undefined` 会依次与 `{}`进行 `&` 运算
* string & {} 得到 string
* null & {} 得到 never
* undefined & {} 得到 never
* string | never | never 得到 string
*/
type Nullable<T> = T | null | undefined;
const zhouba: Nullable<string> = null;
type DeapReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeapReadonly<T[P]> : T[P]
}
const wujiu: DeapReadonly<{
name: string,
info: {
addr: string
}
}> = {
name: 'wujiu',
info: {
addr: 'shanghai'
}
}
// wujiu.info.addr = 'nanjing' // 这里会报错不让改
Pick
Extract
Exclude
Omit
Merge
可以用 |
实现Comple
可以用 Exclude
反过来运算ts// 挑选 Pick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
}
const zs: MyPick<{
name: string,
age: number,
}, 'name'> = {
name: 'zs',
// age: 12, // 这里不允许
}
// TS内置的交叉并补是针对字面量类型
// 交集 Extract T和U共同的字面量
type MyExtract<T, U> = T extends U ? T : never;
const ls: MyExtract<'a'|'b', 'b'> = 'b'; // 只能是'b', 不能写'a'
// 差集 Exclude T - U
type MyExclude<T, U> = T extends U ? never: T
const ww: Exclude<'a' | 'b', 'b'> = 'a'; // 只能是'a', 不能写'b'
// 并集 用联合类型实现即可
type Merge<T, U> = T | U;
const words: Merge<'a' | 'b', 'b'| 'c'> = 'c' // 也可以是'a'或'b';
// 补集 差集反着来
type Comple<T, U> = U extends T ? never : U
const words2: Comple<'a' | 'b', 'b' | 'c'> = 'c' // 只能是 'c'
// 踢出Omit Pick的反义词
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
}
const zl: MyOmit<{
name: string;
age: number;
sex: 1 | 2
}, 'sex' | 'age'> = {
name: 'ls',
// age: 23, // 不允许
}
tstype Copy<T> = {
[P in keyof T]: T[P]
}
interface Person {
readonly name: string;
age?: number;
sex: 1 | 2;
}
const p: Copy<Person> = {
name: 'zs',
sex: 1,
}
p.age = 12
// p.name = 'ls'; // 无法为“name”赋值,因为它是只读属性。ts(2540)
tsinterface A {
name: string;
age: number;
}
interface B {
age: number;
sex: 1 | 2
}
interface C {
name: string;
age: number;
sex?: 1 | 2;
}
// 要求 C由A和B得到
type C1<A, B> = {
[P in keyof A] : A[P];
} & {
[P in Exclude<keyof B, keyof A & keyof B>]?: B[P]
}
const c: C1<A, B> = {
name: 'zs',
age: 12,
}
c.sex = 1;
ts// 合并接口,判断符合条件的值是否存在, 不存在就是never
interface A {
a: string;
b: number;
}
interface B {
a: number;
b: number;
c: boolean;
}
// C类型是A类型和B类型运算得到
interface C {
b: number;
}
/**
* 思路分析:
* - A.b与B.b属性值相同,要过滤掉属性b,可以通过映射类型过滤,但是这里的难点是根据值类型过滤
* - 如果是单纯过滤值类型的过来可以根据字面量 {[P in T]: T[P]}[keyof T]
* - 我们可以把属性值类型相同的属性作为字面量原封不动的赋值给值,不相同的属性其值赋值为 never 在过滤掉 never, 得到属性字面量集合
*/
// 过滤T类型中属性值为U类型的Key
type Keys<T, U> = {
[P in keyof T]: P extends keyof U
? T[P] extends U[P]
? P
: never
: never
}[keyof T]
type AB<A, B> = {
[P in Keys<A, B>]: A[P]
}
var a: AB<A, B> = {
// a: 'zs', // 这里报错
b: 23,
}
// 方法二
type MyExtract<A extends object, B extends object> = {
[P in keyof A as P extends keyof B
? A[P] extends B[P]? P : never
: never
]: A[P]
}
泛型固然强大,但描述多个泛型之间的约束关系有限,通常是表示一对一关系的约束,多个泛型之间的关系参数类型及返回值类型之间的关系无法描述,这里就需要infer
(约束类型)登场了。
infer
语法 T extends U ? X : Y
infer
只能在条件类型的extends
子句中使用, (必须在entends
后面)infer
得到的类型只能在true
语句中使用,即X
中使用ReturnType
、InstanceType
、Parameters
ts// ReturnType
function getStr(arg: number | boolean | string | object | null | undefined) {
if(typeof arg === 'string') {
return arg;
}
if(typeof arg === 'undefined') {
return '';
}
if(typeof arg === 'object' && !arg) {
return '';
}
return arg!.toString();
}
var a: ReturnType<typeof getStr> = 'abc' // a: string
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
var b: MyReturnType<typeof getStr> = 'ab' // b: string
// Parameters 获取参数类型
// type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
// 我们写个不一样的,获取第二个参数的类型
type GetSecondParam<T extends (...args: any) => any>
= T extends (first: any, second: infer S, ...rest: any) => any
? S : never;
function getSecondParam(a: string, b: number) {
console.log(a, b);
}
const secondType: GetSecondParam<typeof getSecondParam> = 123 // secondType: number
// InstanceType 获取实例类型
type MyInstanceType<T extends new (...args: any) => any>
= T extends {
new (...args): infer R
} ? R : any;
let obj: MyInstanceType<{
new (...args: any): {
name: string;
age:number;
}
}>; // obj: { name: string; age: number; }
Promise
成功值的类型TS// infer可以获取数组类型
type ArrayType<T> = T extends (infer R)[] ? R : any;
var arr: ArrayType<number[]>; // arr: number
// 获取数组最后一个元素的类型
type ArrayLast<T> = T extends [...infer _, infer Last] ? Last: never;
var last: ArrayLast<[string, 's', false]>; // last: false // 获取元组最后一个类型
// 推断Promise成功值的类型
type InferPromise<T> = T extends Promise<infer T> ? T : never;
type Resp = InferPromise<Promise<string>>; // Resp: string
// 推断字符串字面量类型的第一个字符对应的字面量类型
type InferString<T extends string>
= T extends `${infer First}${infer _}`
? First
: [];
type word = InferString<'First' | 'Second'>; // word: 'F' | 'S'
// 综合案例
type Shift<T> = T extends [infer First, ...infer Rest] ? [...Rest] : [];
var a1: [number, string] = [1,'2'] ;
const result: Shift<[number, string]>[0] = 'str'; // result: string
type Pop<T extends any[]> = T extends [...infer L, infer R] ? [...L] : [];
type Reverse<T extends unknown[], U extends unknown[] = []>
= T extends []
? U
: T extends [infer L, ...infer R]
? Reverse<R, [L, ...U]>
: U;
type A2 = Reverse<[string, number, boolean]>; // A2: [boolean, number, string]
type FlipArguments<T extends Function>
= T extends (...arg: infer R) => infer S
? (...arg : Reverse<[...R]>) => S
: T;
type StartsWith<T extends string, U extends string>
= T extends `${U}${infer R}` ? true : false;
type TrimLeft<S extends string>
= S extends `${infer L}${infer R}`
? L extends ' ' | '\n' | '\t'
? TrimLeft<R>
: S
: '';
type Trim<S extends string>
= S extends `${' ' | '\t' | '\n'}${infer R}`
? Trim<R>
: S extends `${infer L}${' ' | '\t' | '\n'}`
? Trim<L>
: S;
type StringToUnion<T extends string, U = never>
= T extends ''
? U
: T extends `${infer L}${infer R}`
? StringToUnion<R, U | L>
: U;
var str: StringToUnion<'abc'> // str: 'a' | 'b' | 'c'
下一篇将记录TS类型体操刷题,在此之前先总结一下类型编程的知识点及技巧,这样刷题的时候不至于太郁闷。
extends
实现条件分支逻辑T extends T
VS [T] extends [never]
T extends true
tstype TestExtends<T> = T extends T ? true : false;
// 传入基本类型都是true 包括 boolean number string bigInt null undefined void any unknown
// 传入 Symbol 得到 true
// 传入never得到never 那怎样判断never呢?
type isNever<T> = [T] extends [never] ? true : false;
type TestExtendsTrue<T> = T extends true ? true: false;
// 只有 never 和 true 会得到true 其他都是false
extends
会进行展开运算 被称为条件分支类型tstype StringOrNumber<T> = T extends string ? string : number;
type C = StringOrNumber<'world' | 7>; // 类型为 string | number
T extends {[k: string]: never}
never
[T] extends [never] ? true : false
tstype IsUnion<T, C extends T = T>
= (
T extends T
? C extends T
? true
: unknown // 联合类型
: never
) extends true ? false : true;
type A = unknown extends true ? false : true; // true
type B = never extends true ? false : true; // false
type C = true extends true ? false : true; // false
keyof T
获取对象类型key
得到字符串字面量类型T[keyof T]
获取对象类型所有Value
得到的联合类型[P in keyof T]
映射key
{[P in keyof T as P extends U ? P : never ]}
将key
变为never
进而实现过滤key
的效果,该方式同样可以过滤值类型 具体案例 TS类型体操Readonly2 题目 答案T[number]
获取数组元素的枚举值in
tsinterface Per{
name: string;
}
type AddProp = <T, K extends string>(
obj: T,
key: K extends keyof T ? never: K,
val: any,
) => any;
const addProp: AddProp = function(obj, key, val) {
// ...
}
addProp({name: 'zs'}, 'age', 12); // ok
addProp({name: 'zs'}, 'name', 'ls'); // 报错 不能添加重复属性
string
与 number
的转换tstype ParseInt<T extends string> = T extends `${infer Digit extends number}` ? Digit : never
ts// 类型转换问题 仔细看三种Omit的问题
// 该案例在类型编程中很有意义
type MyOmit1<T, K extends keyof T = keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
}
type MyOmit2<T, K extends keyof T = keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
}
type MyOmit3<T, K extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, K>>
interface User {
name?: string
age?: number
// readonly age?: number
}
type AA = MyOmit1<User, 'name'> // AA: { age: number | undefined; }
type BB = MyOmit2<User, 'name'> // BB: { age?: number | undefined; }
type CC = MyOmit3<User, 'name'> // BB: { age?: number | undefined; }
// 总结, 直接在对象里通过范型函数过滤属性的方式 会丢失属性的 可选、可读 特性
本文作者:郭敬文
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!