我在之前一篇关于泛型的文章中提到,当我们需要写一个返回值类型与入参类型保持一致(即传入什么类型就返回什么类型)的函数时可以使用泛型,如下:
function identity<Type>(arg: Type): Type {
return arg;
}
但是假如我们需要的不是保持一致;而是如果传入string
类型那么返回类型就是{ name: string; }
,如果传入number
类型那么返回值就是{ id: number }
,这种情况应该如何写呢?下面我们将来解决这个问题。
TypeScipt中的三元表达式
在js中我们经常会看到以下代码,以下代码描述了当a
大于1时isLargeThanOne
的值就是true
,否则isLargeThanOne
的值为false
const isLargeThanOne = a > 1 ? true : false
而在ts条件类型中也有相似的语法,请看下面代码:
interface IdLabel {
id: number;
}
interface NameLabel {
name: string;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
function createLabel<T extends string | number>(idOrName: T): NameOrId<T> {
// ...
}
let a = createLabel("typescript"); // NameLabel
let b = createLabel(2); // IdLabel
ts中的条件类型与js中的三元表达式十分相似,唯一需要理解一下的是extends
关键字。当extends
左边的类型可以赋值给右边类型时,你得到的类型就是第一个分支(true分支)的类型,否则你得到的就是第二个分支(false分支)的类型。
条件类型约束
如下代码,当我们尝试对一个未知类型读取message
时ts会抛出警告;因为ts编译器并不认为T
是具有message
属性的。
type MessageOf<T> = T['message']; // Type '"message"' cannot be used to index type 'T'.
我们可以通过extends
关键字来约束传入的T
必须带有message
属性。
type MessageOf<T extends { message: unknown }> = T['message'];
interface Email {
message: string;
}
type EmailMessageContents = MessageOf<Email>; // string
type n = MessageOf<number>; // - Type 'number' does not satisfy the constraint '{ message: unknown; }'.
然而当我们想要T
可以是任意类型,当T
不含有message
属性时返回never
应该如何实现呢?请看下面代码:
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never;
type EmailMessageContents = MessageOf<Email>; // string
type n = MessageOf<number>; // never
infer
如下实现了一个flatten
函数,当传入是数组时返回数组的第一个元素,否则返回入参本身。
function flatten(arg) {
return Array.isArray(arg) ? arg[0] : arg
}
那么在ts中如何描述这个函数呢?请看下面代码
type Flatten<T> = (arg: T) => T extends any[] ? T[number] : T
这样实现似乎很完美,但是当我们想要在很深的结构中取出我们想要的类型就会比较麻烦,可能是这样的T[number]['a'][number]['b'][number]['c']...
。
ts提供了infer
关键字可以解决这个问题,请看下面代码:
type Flatten<T> = (arg: T) => T extends Array<infer Item> ? Item : T
infer
关键字使我们可以声明式的引入一个新的泛型变量,来使得我们可以不必考虑如何分离和挖掘我们感兴趣的类型的结构。在这个例子中本质上就相当于type Item = T[number]
infer
关键字可以帮助我们写出很多工具类型,如果下面的例子,提取出函数的返回值:
type ReturnType<T> = T extends (...args: never[]) => infer Return ? Return : never
type Num = ReturnType<() => number>; // number
总结
js相较于其他如c/java等语言来说性能是确实不如的,但是js强大的灵活性使得我们可以秀出很多“骚操作”。ts虽然引入了静态类型检查,但是却没有影响到灵活性。其很大的原因要归功于泛型和条件类型的存在。