掌握TypeScript的条件类型,了解TypeScript内置的实用类型是如何工作的。
您是否使用过Exclude、Extract、NonNullable、Parameters和ReturnType实用程序类型? 你知道他们内部是怎么运作的吗? 事实上,上述TypeScript内置的实用程序类型都是基于条件类型开发的。
注意:只演示过程的一部分
在这里,我们首先简要了解这些TypeScript内置实用程序类型的具体实现。
这些实用程序类型用于以下目的:
Exclude:通过从?UnionType
?中排除可赋值给?ExcludedMembers
?的所有联合成员来构造类型。
Extract:通过从?Type
?中提取可赋值给?Union
?的所有联合成员来构造类型。
NonNullable:通过从?Type
?中排除?null
?和?undefined
?来构造一个类型。
形参:从函数类型?Type
?的形参中使用的类型构造一个元组类型。
ReturnType:构造一个由函数?Type
?返回类型组成的类型。
下面我们来看几个用法示例:
如果您想彻底掌握它们并创建自己的实用程序类型,那么不要错过本文所涵盖的内容。
前面描述的内置实用程序类型在内部使用了TypeScript 2.8中引入的条件类型。该类型的语法如下:
T extends U ? X : Y
T、U、X和Y都是类型占位符。您可以这样理解语法:当类型T可以分配给类型U时,则返回类型X,否则返回类型y。看到这里,您会想起JavaScript中的三元表达式。
那么条件类型有什么用呢?让我们举个例子。
type IsString<T> = T extends string ? true : false;
type I0 = IsString<number>; // false
type I1 = IsString<"abc">; // true
type I2 = IsString<any>; // boolean
type I3 = IsString<never>; // never
在上面的代码中,我们定义了IsString实用程序类型。使用此实用程序类型,我们可以确定传递给类型参数T的实际类型是否为字符串类型。除了使用条件类型和条件链确定单个类型外,我们还可以同时确定多个类型。
接下来,让我们看看如何实现这个实用程序类型:
在上面的代码中,我们定义了一个新的TypeName实用程序类型,并在其中使用条件链。为了使您更容易理解条件链,让我们以JavaScript三元表达式为例来演示它的作用。
现在问题来了,对于前面定义的TypeName实用程序类型,如果传入的类型是联合类型,将返回什么结果? 让我们验证以下内容。???????
// "string" | "function"
type T10 = TypeName<string | (() => void)>;
??????
// "string" | "object" | "undefined"
type T11 = TypeName<string | string[] | undefined>;
为什么T10和T11类型返回联合类型? 这是因为TypeName是一个分布式条件类型。在条件类型中,如果被检查的类型是一个“裸”类型参数,即没有被Array、Tuple、Promise等包装,则该条件类型称为分布式条件类型。
对于分布式条件类型,当传入的检查类型为联合类型时,在操作过程中会将其分解为多个分支。
T extends U ? X : Y
T => A | B | C
A | B | C extends U ? X : Y =>
(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
为了让您更容易理解,让我们举一个例子。
从上面的结果可以看出,如果将类型参数T包装在条件类型中,则该条件类型不是分布式条件类型,因此在操作过程中不会将其分解为分支。
在学习了条件类型和分布式条件类型之后,让我们举一个例子来演示TypeScript内置实用程序类型Exclude的执行流程。
type Exclude<T, U> = T extends U ? never : T;
???????
type T4 = Exclude<"a" | "b" | "c", "a" | "b">
("a" extends "a" | "b" ? never : "a") // => never
| ("b" extends "a" | "b" ? never : "b") // => never
| ("c" extends "a" | "b" ? never : "c") // => "c"
never | never | "c" // => "c"
一旦掌握了条件类型,就可以通过将它们与前几篇文章中介绍的映射类型相结合来实现一些有用的实用程序类型。
例如,实现诸如?FunctionProperties
?和?NonFunctionProperties
?这样的实用程序类型。
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface User {
id: number;
name: string;
age: number;
updateName(newName: string): void;
}
type T5 = FunctionPropertyNames<User>; // "updateName"
type T6 = FunctionProperties<User>; // { updateName: (newName: string) => void; }
type T7 = NonFunctionPropertyNames<User>; // "id" | "name" | "age"
type T8 = NonFunctionProperties<User>; // { id: number; name: string; age: number; }
在上面的代码中,使用上面的实用程序类型,我们可以很容易地在User对象类型中提取函数类型和非函数类型属性以及相关的对象类型。
读完这篇文章,我相信你已经理解了条件类型和分布式条件类型的作用,也知道了一些实用程序类型是如何在TypeScript中实现的。关于如何在条件类型中使用infer来实现类型推断,我们将在后续文章中介绍。
?欢迎关注公众号:文本魔术,了解更多