本文首发于集体博客
泛型
首先, 咱们来写一个函数: loggerNum 函数, 这个函数的作用是 console.log 输出数字值, 而后将该值返回:
const loggerNum = (params: number) => {console.log(params);
return params;
};
loggerNum(1); // number
loggerNum('1'); // Error: 字符串不能调配给数字类型
如果说咱们还须要打印字符串呢?
const loggerStr = (params: string) => {console.log(params);
return params;
};
loggerStr('1'); // string
如果说咱们还有布尔类型, 害, 是不是得持续申明一个函数来实现, 算了, 我应用 any:
const logger = (params: any) => {console.log(params);
return params;
};
// logger(1); // any
// logger('1'); // any
// logger(false); // any
这种形式当然没有问题, 然而它失去了原有的类型以及类型查看(不到万不得已, 请不要应用 any), 所以咱们须要一种捕获参数类型的形式, 以便咱们能够应用它来示意返回的内容.
function logger<Type>(params: Type): Type {console.log(params);
return params;
}
咱们当初给 logger
函数增加来一个类型变量 Type
, 而后Type
能够捕捉到提供的类型 (比如说number
、string
…), 前面咱们就能够应用该Type
, 在这里咱们应用Type
作为参数和返回值, 所以 Ts 会查看其返回值是不是与 Type
是统一的.
logger<number>(1); // number
logger<string>('1'); // string
logger<boolean>(true); // boolean
logger<number[]>([1, 2, 3]); // number[]
logger<string>(1); // 数字类型不能调配给字符串类型
咱们也能够应用类型参数主动推断的形式, ts 编译器会依据咱们传入的参数主动设置其类型.
logger(1); // number
logger('1'); // string
logger(true); // boolean
logger([1, 2, 3]); // number[]
咱们第一个简略的泛型函数就实现啦~.
泛型束缚
咱们再拿下面那个例子来说, 咱们想打印一下参数的长度, 改一下代码:
function logger<Type>(params: Type): Type {console.log(params.length); // Property 'length' does not exist on type 'Type'.
return params;
}
报错了, 别慌乱, 因为应用泛型, 咱们此时不能拜访它的任何属性, 所以这个时候报错是失常的, 做一个类比, 咱们把之前的例子比喻成一个充电器, 什么充电器都能够, 只有是充电器就好了, 当初充电器加了束缚(类型束缚), 只能适配 type-c 充电器, 不是 type-c 充电器都不行.
这里也是一样的, 咱们心愿参数把类型做一个限度, 至多具备 length
属性的类型才能够传入, 所以来看看代码.
function logger<Type extends {length: number}>(params: Type): Type {console.log(params.length);
return params;
}
Type extends {length:number}
就是做了一个类型束缚, 只有具备 length
属性的对象才能够传入.
logger({length: 0});
logger([1, 2, 3]);
logger(1); // number 不能调配给 {length:number}
在泛型束缚中应用类型参数
有了下面的根底, 咱们再来实现一个办法 getProperty(obj, key)
返回对象中的指定 key 的 value.
function getProperty<Type>(obj: Type, key: keyof Type) {return obj[key];
}
keyof 前面会介绍
默认类型参数
在 Js 中有默认参数, 如果没有传值时应用该默认值.
const inc = (count, step = 1) => count + step;
inc(1); // 2
inc(1, 5); // 6
而在 Ts 中有默认类型参数, 如果没有传入参数就应用默认类型参数, 下面 getProperty
的例子更新一下:
function getProperty<Type, Key = keyof Type>(obj: Type, key: Key) {return obj[key]; // Key 不能当作 Type 的索引
}
胜利的报错了, Key
在不传入类型的时候, 才会是默认的 keyof Type
, 而如果Key
如果传入了 number
、string
等类型时, Key
就会采纳传入的类型:
getProperty({name: 'senlin', age: 18}, false); // getProperty(obj: Person, key: boolean)
getProperty({name: 'senlin', age: 18}, '444'); // getProperty(obj: Person, key: string)
所以咱们须要对 Key
做一个参数类型束缚:
function getProperty<Type, Key extends keyof Type = keyof Type>(
obj: Type,
key: Key
) {return obj[key];
}
getProperty({name: 'senlin', age: 18}, false); // Error: false 不能调配给 'name'|'age'
getProperty({name: 'senlin', age: 18}, '444'); // Error: '444' 不能调配给 'name'|'age'
getProperty({name: 'senlin', age: 18}, 'name'); // OK: 'senlin'
穿插类型 &
类型运算符 & 用于创立穿插类型:
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// "b" | "c"
type Intersection = A & B;
如果咱们将类型 A 和类型 B 视为汇合, 那么 A & B 就是两个汇合的交加, 换句来说: 后果的成员是两个操作数的成员.
与 never、unknown 的爱恨情仇
type A = 'a' | 'b';
type D = A & never; // never
type E = A & unknown; // 'a' | 'b'
如果把 ts 的类型当作一个汇合来看的话, unknown 相当于汇合中的选集, 它是一个顶部类型:
- 空集 (never) 和其余汇合 (A) 做交加(穿插类型) = 空集(never).
- 选集 (unknown) 和其余汇合 (A) 做交加(穿插类型) = 其余类型.
联结类型 |
咱们有这样子的一个函数, 承受一个参数, 如果是数组, 则原样返回, 如果不是数组, 将值包裹成数组.
function wrapToArray(params: number) {return [params];
}
wrapToArray(1); // number[]
wrapToArray([1]); // number[] 不能调配给 number
所以这个时候, 咱们就须要采纳联结类型, 冀望参数能够传入数字和数字数组.
function wrapToArray(params: number | number[]) {
// 类型放大
if (Array.isArray(params)) return params;
return [params];
}
wrapToArray(1); // number[]
wrapToArray([1]); // number[]
留神这里的参数 number|number[]
, 意思就是容许传入数字和数字数组类型, 这里咱们应用Array.isArray
来进行类型放大, 如果是数组就间接返回, 如果不是数组就进行包裹一层返回.
为什么要进行类型放大? 因为 Ts 在应用过程中须要明确具体的类型 (any 除外!), 以后类型是
number|number[]
, 并不分明是number
类型还是number[]
类型, 所以须要应用Array.isArray
来将其放大到number[]
类型.
咱们也能够应用 |
来创立联结类型:
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// "a" | "b" | "c" | "d"
type Union = A | B;
如果把 A 和 B 当作两个汇合, 联结类型就是求两个汇合的并集, 后果的成员是至多一个操作数的成员.
如果咱们对一个对象类型进行 keyof
操作的时候, 也会失去联结类型:
type Person = {
name: string;
age: number;
};
type PersonKeys = keyof Person; // 'name' | 'age'
对象类型的联结
因为联结类型的每个成员都是至多一个组件类型的成员, 咱们只能平安的拜访所有组件类型共享的属性(A 行). 如果要拜访其余属性, 咱们须要一个类型爱护(B 行):
type Person = {
name: string;
phone: string;
};
type Teacher = {
name: string;
project: string;
};
type Union = Person | Teacher;
function fn(params: Union) {params.name; // (A 行) OK
// 报错: Property 'phone' does not exist on type 'Union'.
params.phone; // error
// (B 行) type guard
if ('project' in params) {
// Teacher
params;
// string
params.project;
} // (C 行) type guard
if ('phone' in params) {
// Person
params;
// string
params.phone;
}
}
联结类型与 never、unknown 的爱恨情仇
type A = 'a' | 'b';
type B = A | never; // 1: 'a' | 'b'
type C = A | unknown; // 2: unknown
如果把 ts 的类型当作一个汇合来看的话, never 相当于汇合中的空集:
- 空集 (never) 和其余汇合 (A) 做并集(类型联结) = 其余汇合(A).
- 选集 (unknown) 和其余汇合 (A) 做并集(类型联结) = 选集(unknown).
所以很多时候, 咱们在应用联结类型、条件类型时会和
never
一起应用.
条件类型
根本格局:
Type2 extends Type1 ? ThenType : ElseType
如果 Type2
类型可能调配给 Type1
类型的话, 返回 ThenType
否则就是 ElseType
类型, 相当于是类型版本的三目运算符.
例子一: 仅包装带有 length 属性的值.
在上面例子中, 如果类型能够调配给 {length:number}
的时候把它包装成一个元素的元祖:
type Wrap<T> = T extends {length: number} ? [T] : T;
type A = Wrap<string>; // [string]
type B = Wrap<number>; // number
调配性查看
咱们能够应用条件类型来判断调配性查看:
type IsAssignableTo<A, B> = A extends B ? true : false;
// true
type Result1 = IsAssignableTo<123, number>;
条件类型是分布式的
条件类型是分布式的:将条件类型利用 C 到联结类型与利用到每个组件的 U 联结雷同。这是一个例子:CU
type Wrap<T> = T extends {length: number} ? [T] : T;
type A1 = Wrap<number | string | boolean>;
// 等同于
type A2 = Wrap<number> | Wrap<string> | Wrap<boolean>;
// 等同于
type A3 = number | boolean | [string];
用一个不太失当的比喻就是: 乘法分配律a*(b+c) = a*b + a*c
.
对于分布式条件类型, 能够应用 never 疏忽某一个后果
再来看一下上面这段代码:
type Wrap<T> = T extends {length: number} ? [T] : never;
type A1 = Wrap<number | string | boolean | number[]>;
// 等同于
type A2 = Wrap<number> | Wrap<string> | Wrap<boolean> | Wrap<number[]>;
// 等同于
type A3 = never | [string] | never | [number[]];
// 等同于
type A4 = [string] | [number[]];
递归条件类型
在 Js 中, 能够看到在任意级别展平和构建容器类型的函数是很常见的. 比如说 .then()
返回的 Promise
会进行开展, 直到找到一个不是 ”promise-like” 的值, 而后将该值传递给回调,
再比如说, 咱们想编写一个类型来获取嵌套数组的元素类型 deepFlatten.
type ElementType<T> = T extends ReadonlyArray<infer U> ? ElementType<U> : T;
declare function deepFlatten<T extends readonly unknown[]>(x: T): ElementType<T>[];
deepFlatten([1, 2, 3]); // number[]
deepFlatten([[1], [2, 3]]); // number[]
deepFlatten([[1], [[2]], [[[3]]]]); // number[]
判断元素 T
是否能够调配给ReadonlyArray
, 如果能够调配就进行递归操作ElementType<U>
, 直到不能调配为止.
咱们还能够编写一个 Awaited
类型来开展 Promise
获取最初的类型.
type Awaited<T> = T extends PromiseLike<infer U> ? Awaited<U> : T;
type P1 = Awaited<Promise<string>>; // string
type P2 = Awaited<Promise<Promise<string>>>; // string
type P3 = Awaited<Promise<string | Promise<Promise<number> | undefined>>>; // string | number | undefined
infer
在 extends 条件类型的子句中, 当初能够有 infer 引入要推断的类型变量的申明. 这种推断的类型变量能够在条件类型的实在分支中援用. 同一个类型变量能够有多个 infer 地位.
大白话就是: 在 extends 条件类型中, 在实在分支中, 申明一个类型变量先占住这个坑位, 具体类型由传入参数的类型决定.
所以 infer 关键字容许咱们从条件类型中推断出另外一个类型, 比如说:
type UnpackArrayType<T> = T extends (infer R)[] ? R : T;
type T1 = UnpackArrayType<number[]>; // number
type T2 = UnpackArrayType<string>; // string
UnpackArrayType
是一个条件类型, 如果 T
能够调配给(infer R)[]
, 就返回这个R
, 否则就返回T
.
T1: UnpackArrayType
中的条件是正确的, 申明一个类型变量 R
占住这个坑位, 因为 number[]
与(infer R)[]
匹配, 所以类型变量 R
就是传入的类型 number
, 而后作为推断过程的后果返回. infer
的作用是通知编译器在 UnpackArrayType
范畴内申明了一个新的类型变量 R, 而后具体类型由传入的类型进行填充 .
T2: UnpackArrayType
中的条件是不成立的, 因为 string
并不能调配给 (infer R)[]
, 所以间接就返回T
, 也就是返回对应的string
类型.
ReturnType
利用 infer
咱们就能够实现ReturnType
, 返回一个函数的后果类型:
type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;
MyReturnType<() => string> // string
MyReturnType<string> // never
如果 T
能够调配给一个函数的话, 咱们申明一个类型变量 R
, 占住这个坑位, 而后在咱们传过来的类型是string
, 所以这个R
的类型就是string
.
infer 只能在 extends 的实在分支下面援用:
type MyReturnType<T> = T extends (...args: any) => infer R ? never : R; // Error: 找不到名称 R
infer 不能在束缚子句中对惯例类型参数应用申明:
type MyReturnType<T extends (...args: any) => infer R> = R; // Error: 并不反对
如果想在类型参数下面应用的话, 无妨试试这种形式:
type MyReturnType<T, L = T extends (...args: any) => infer R ? R : never> = L; // OK
MyReturnType<() => string> // string
对 key 进行重命名
interface ApiData {
'maps:person': string;
'maps:age': boolean;
address: string;
}
type RemoveMapsFromObj<T> = {[P in keyof T as RemoveMaps<P>]: T[P];
};
type RemoveMaps<T> = T extends `maps:${infer S}` ? S : T;
type RemovedPerson = RemoveMapsFromObj<ApiData>;
映射类型
映射类型通过循环某一组 key 来生成一个对象:
type Obj = {[K in 'name' | 'address']: string;
};
// {name:string; address:string}
如果咱们本人要实现一个 Partial
呢?
type MyPartial<T> = {[P in keyof T]+?: T[P];
};
type Person = {
name: string;
age: number;
};
type PartialPerson = MyPartial<Person>;
// {name?:string; age?:number}
咱们来一点点剖析一下:
type PartialPerson = {[P in keyof Person]+?: T[P];
};
而后分解成keyof Person
:
type PartialPerson = {[P in 'name' | 'age']+?: T[P];
};
咱们再来将合成进去的 key 进行下一步操作:
type PartialPerson = {['name']+?: T['name'];
['age']+?: T['age'];
}
+?
的意思就是增加一个 ?
符号, 也就是可选项属性的意思, 所以最初的后果:
type PartialPerson = {
name?: string;
age?: string;
};
映射类型中 key 的从新映射
映射类型只能应用咱们提供的键生成新的对象类型, 然而很多时候咱们心愿依据输出创立一个新的 key 或者过滤掉某一些 key.
在下面的例子中, 不想要人晓得我的年龄和手机号, 在 Person
中去掉这两个字段的申明.
interface Person {
name: string;
age: number;
address: string;
phone: string;
}
type RemoveSecretProps<T> = {[P in keyof T as P extends 'age' | 'phone' ? never : P]: T[P];
};
// 应用 Exclude 进行简化
type RemoveSecretProps2<T> = {[P in keyof T as Exclude<P, 'age' | 'phone'>]: T[P];
};
type SecretPerson = RemoveSecretProps<Person>;
/*
{
name: string
address: string
}
*/
它的运行流程:
type RemoveSecretProps = {['name' extends 'age' | 'phone' ? never : 'name']: Person['name']
['address' extends 'age' | 'phone' ? never : 'address']: Person['address']
['age' extends 'age' | 'phone' ? never : 'age']: Person['age']
['phone' extends 'age' | 'phone' ? never : 'phone']: Person['phone']
}
// 等同于
进一步:
type RemoveSecretProps = {['name']: Person['name'];
['address']: Person['address'];
};
最初:
type RemoveSecretProps = {
name: string;
address: string;
};
当 as 子句中指定的类型解析为never
, 不会为该键生成任何属性. 因而, as 子句能够用作过滤器.
模版文字类型
模版文字类型是建设在字符串文字类型之后, 并且能够通过联结扩大出其余字符串, 容许咱们对须要一组特定字符串的函数和 API 进行建模.
它的语法和 Js 中的模版字符串一样, 然而是用于类型:
type World = 'world';
type Hi = `hello ${World}`; // hello world
function setAlignment(location: 'top' | 'middle' | 'bottom') {}
setAlignment('middel'); // middel 不能调配给 top | middle | bottom
- 如果模版文字类型是联结类型的话, 占位符中的联结类型散布在模板文字类型上. 例如
[${A|B|C}]
解析为[${A}]
|[${B}]
|[${C}]
. 多个占位符中的联结类型解析为叉积。例如[${A|B},${C|D}]
解析为[${A},${C}]
|[${A},${D}]
|[${B},${C}]
|[${B},${D}]
. - 占位符中的
string
、number
、boolean
和bigint
文字类型会导致占位符被文字类型的字符串示意模式替换。例如[${'abc'}]
解析为[abc]
和[${42}]
解析为[42]
. - 占位符中的任何一种类型
any
、string
、number
、boolean
和bigint
都会导致模板文字解析为string
类型。 - 占位符中的类型
never
类型导致模板文字解析为never
.
type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;
type ToString<T extends string | number | boolean | bigint> = `${T}`;
type T0 = EventName<'foo'>; // 'fooChanged'
type T1 = EventName<never>; // never
type T2 = EventName<'foo' | 'bar' | 'baz'>; // 'fooChanged' | 'barChanged' | 'bazChanged'
type T3 = Concat<'Hello', 'World'>; // 'HelloWorld'
type T4 = `${'top' | 'bottom'}-${'left' | 'right'}`; // 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
type T5 = ToString<'abc' | 42 | true | -1234n>; // 'abc' | '42' | 'true' | '-1234'
请留神, 联结类型的穿插积散布可能会迅速降级为十分大且老本昂扬的类型. 另请留神, 联结类型限度为少于 100000 个成分, 以下将导致谬误:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Zip = `${Digit}${Digit}${Digit}${Digit}${Digit}`; // Error
因为 Zip
是 0-9 的穿插积 > 100000, 所以会报错.
type Digit = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Zip = `${Digit}${Digit}${Digit}${Digit}${Digit}`; // OK
文字模版类型来生成属性的 change 的办法
type OnPropsChangeMethod<T> = {[K in keyof T & string as `on${Capitalize<K>}Change`]: (value: T[K]) => void;
};
declare function makeWatchObject<T>(obj: T): T & OnPropsChangeMethod<T>;
const person = makeWatchObject({
name: 'senlin',
age: 18,
});
person.onNameChange = (name) => {console.log('newName:', name);
};
person.onAgeChange = (age) => {console.log('newAge:', age);
};
文字模版类型实现一个简略的字符串模版, 来实现值的类型提醒.
type Placeholder<S> = S extends `${string}{${infer P}}${infer Rest}`
? P | Placeholder<Rest>
: never;
declare function format<S extends string>(
template: S,
args: Record<Placeholder<S>, unknown>
): string;
const f = format(`name: {name} , age: {age}`, {name: 'senlin', age: 18}); // format(string, { name: unknown, age: unknown})
其余操作符号
索引类型查问运算符 keyof
在下面的例子中, 咱们曾经应用过 keyof
关键字, 在这里, 咱们再来看看keyof
, 它能够列出对象的属性 key.
type Person = {
name: 'senlin';
age: 18;
};
type PersonKeys = keyof Person; // 'name' | 'age'
如果将 keyof
用于数组, 你会发现后果出其不意:
type ArrayKeys = keyof ['a', 'b', 'c'];
// number | '0' | '1' | '2' | 'length' | 'pop' | 'push' ...
后果:
- 元组元素的索引,作为字符串:”0″ | “1” | “2”
- number 索引属性的类型
- length
- Array 办法的办法
空对象的属性键是空集never
:
type ObjKeys = keyof {}; // never
这是 keyof 解决穿插类型和联结类型的形式:
type A = {a: number; common: string};
type B = {b: number; common: string};
type Result1 = keyof (A & B); // 'a' | 'b' | 'common'
type Result2 = keyof A | keyof B; // 'a' | 'b' | 'common'
type Result3 = keyof (A | B); // 'common'
type Result4 = keyof A & keyof B; // 'common'
类型查问运算符 typeof
typeof 是将获取值转化为 ts 类型.
const value = 'value';
type Value = typeof value; // 'value'
第一个值 value
是 value 变量的值, 第二个 value
是 value 变量的类型.
const add = (a: number, b: number) => a + b;
type Add = typeof add; // (a:number, b:number) => number
索引拜访运算符 T[K]
索引拜访运算符返回其键可调配给 T[K]的所有属性的类型, 也称为查找类型.
type Person = {
name: 'senlin';
age: 18;
};
type Name = Person['name']; // 'senlin'
type Age = Person['age']; // 18
type NameAndAge = Person['name' | 'age'];
// 'senlin' | 18;
[]
中的类型必须是 Person
的属性键 (由keyof
计算得出), 然而如果类型增加了索引签名的话, 咱们就可应用索引类型:string 来读取了.
type Obj = {[key: string]: string;
};
type ObjKeys = keyof Obj; // string | number
KeysOfObj 包含类型 number, 这是因为: JavaScript 在索引对象时将数字转换为字符串:
在读取对象属性时 [..] 应用数字索引时, JavaScript 实际上会在索引到对象之前将其转换为字符串. 这意味着应用 100(number 类型)进行索引与应用 ”100″(string 类型)进行索引是一回事, 因而两者须要保持一致.
const abc = {1: 'one',};
console.log(abc[1] === abc['1']); // true
元组类型也反对索引拜访:
type Tuple = ['a', 'b', 'c', 'd'];
// "a" | "b"
type Elements = Tuple[0 | 1];
括号运算符也是分布式的:
type MyType = {prop: 1} | {prop: 2} | {prop: 3};
// 1 | 2 | 3
type Result1 = MyType['prop'];
// 等同于
type Result2 = {prop: 1}['prop'] | {prop: 2}['prop'] | {prop: 3}['prop'];
// 所以就是 1 | 2 | 3
类型体操
应用 Hook + 泛型能够写出一个公共的 hook, 这里就展现一个简略通用的 api hook.
type RequestResult<T> = {
data: T | null;
error: unknown;
abort: () => void;};
function useFetch<T>(url: string, options?: RequestInit): RequestResult<T> {const [data, setData] = React.useState<T | null>(null);
const [error, setError] = React.useState<unknown>(null);
const [abort, setAbort] = React.useState<() => void>(() => {});
React.useEffect(() => {const fetchData = async () => {
try {const abortController = new AbortController();
const signal = abortController.signal;
setAbort(abortController.abort);
const res = await fetch(url, { ...options, signal});
const json = (await res.json()) as T;
setData(json);
} catch (error) {setError(error);
}
};
fetchData();
return () => {abort();
};
}, []);
return {data, error, abort} as const;
}
const {data, error} = useFetch<{login: string; id: number}>('https://api.github.com/users/itsuki0927');
/*
{
login: string
id: number
}
*/
url 解析成一个对象
须要解析 url 上的搜寻参数时, 发现要么没有类型, 要么就是本人解析进去去指定某一个类型, 能不能通过 Ts 就实现这一层的参数类型解析呢?
type Convert<T> = T extends `${infer Key}=${infer _}` ? Key : T;
type ParseSearchParameters<
T,
L = T extends `?${infer U}` ? U : T
> = L extends `${infer A}&${infer B}`
? {[P in Convert<A | keyof ParseSearchParameters<B>>]: string;
}
: {[P in Convert<L & string>]: string;
};
const url = '?name=senlin&age=18&address=hunan';
declare function parseSearchParameters<T extends string>(params: T): ParseSearchParameters<T>;
parseSearchParameters(url);
/*
{
name: string
age: string
address: string
}
*/
什么都不须要做, 它依据你的搜寻格局主动给你转化成了对应的类型.
Join、Split
模板文字类型能够与递归条件类型组合以编写 Join
和 Split
迭代反复模式的类型.
type Join<T extends string[], U extends string | number> = T['length'] extends 1 ? `${T[0]}` : T extends [infer A extends string, ...infer B extends string[]] ? `${A}${U}${Join<B, U>}` : ''type Split<T extends string, U extends string> = T extends'' ? [] : T extends `${infer A}${U}${infer B}` ? [A, ...Split<B, U>] : [T]
type A1 = Join<['a', 'p', 'p', 'l', 'e'], '-'>
// a-p-p-l-e
type A2 = Split<A1, '-'>
// [a,p,p,l,e]
type B1 = Join<['Hello', 'World'], '-'>
// Hello-World
type B2 = Split<B1, '-'>
// [Hello,World]
type C1 = Join<['2', '2', '2'], ''>
// 222
type C2 = Split<C1, ''>
// [2,2,2]
async Promise
基于原有类型进行增加额定的类型.
type Promisify<T> = T extends (...args: [...infer Rest, (err: any, res?: infer Result) => void]
) => void
? (...args: Rest) => Promise<Result>
: never;
type PromisifyObject<T> = {[P in keyof T & string as Promisify<T[P]> extends never
? never
: `${P}Async`]: Promisify<T[P]>;
};
type FS = {
version: string;
open(path: string, cb: (err: any, n: number) => void): void;
read(fd: string, cb: (err: any, name: string) => void): void;
};
declare const fs: FS;
declare const fsp: PromisifyObject<FS>;
// (string) => Promise<number>
fsp.openAsync('path').then((n) => {console.log(n.toFixed(2));
});
相干材料
参考资料
- 映射类型的 key 从新映射和模版文字类型
- infer 的解释
- 递归条件类型
- keyof 解释
- 汇合、穿插类型、联结类型
- 穿插类型
学习材料
- type-challenges
- ts-toolbelt
- utility-types
- SimplyTyped
- Ts 官网