TypeScript高级模式:条件类型和模板字面量

TypeScript的类型系统是图灵完备的——这意味着你可以完全在类型级别编码复杂逻辑。条件类型和模板字面量类型是编写精确而非仅仅描述性类型的两个最强大的功能。以下是何时以及如何使用它们。

条件类型

语法:`T extends U ? X : Y`。如果`T`可分配给`U`,类型解析为`X`;否则为`Y`。最简单的用法:`type IsString = T extends string ? true : false;`。当你需要根据输入类型不同的类型时有用。`infer`关键字:在条件类型内部用于从模式中捕获类型。示例:`type ReturnType = T extends (…args: any[]) => infer R ? R : never;`——这提取任何函数的返回类型。`infer`可以出现在`extends`的右侧,并创建TypeScript根据匹配内容填写的类型变量。分布式条件类型:当条件类型应用于联合类型时,它在每个成员上分布。`type ToArray = T extends any ? T[] : never;`——`ToArray`计算为`string[] | number[]`,而不是`(string | number)[]`。要防止分布,用元组包装:`type ToArray = [T] extends [any] ? T[] : never;`。条件类型的实际用途:`NonNullable`(内置——从T中删除null和undefined);`Parameters`(提取函数的参数类型);`ConstructorParameters`(构造函数的参数类型);`InstanceType`(构造函数的返回类型——实例类型)。构建自己的:`type Awaited = T extends Promise ? Awaited : T;`——递归地展开嵌套的Promise。`type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;`——使所有嵌套属性变为可选。约束:依赖于`infer`或复杂类型操作的条件类型可能导致TypeScript花费大量时间检查大型代码库——注意大量使用深度递归条件类型会减慢编译器。

模板字面量类型

模板字面量类型(在TypeScript 4.1中引入)从其他字符串类型的组合创建字符串类型:`type Greeting = \`Hello, \${string}\`;`——匹配以”Hello, “开头的任何字符串。与联合结合:`type EventName = \`on\${Capitalize}\`;`。更具体地:`type CSSProperty = ‘margin’ | ‘padding’; type CSSValue = number | string; type CSSDeclaration = \`\${CSSProperty}: \${CSSValue}\`;`——一个匹配”margin: 10px”但不匹配”font-size: 12px”的类型。实际应用——类型化事件名称:`type Events = { click: MouseEvent; focus: FocusEvent; blur: FocusEvent; }; type EventListenerName = \`on\${Capitalize}\`;`——产生`”onClick” | “onFocus” | “onBlur”`。使用模板字面量的实用程序类型:`Uppercase`、`Lowercase`、`Capitalize`、`Uncapitalize`——所有都对字符串类型操作。用模板字面量+infer提取字符串的部分:`type ExtractRoute = S extends \`/\${infer Rest}\` ? Rest : never;`——`ExtractRoute<'/users/profile'>`给出`’users/profile’`。路由参数提取:递归地从路由模式中提取`:id`和`:userId`。

何时值得使用这些功能

条件类型值得复杂性的情况:你正在编写库代码,其中强类型防止了整类用户错误;替代方案是更大的重载集合;你正在编码无法表达为简单联合或交叉类型的不变量。模板字面量类型值得使用的情况:你有遵循可预测模式的字符串枚举值(事件名称、CSS属性、路由模式、方法名称);你想从另一组派生一组有效字符串,而不是手动保持同步。不使用它们的情况:对于频繁变化的应用程序代码——复杂的类型机制维护和理解成本高;当更简单的类型(联合或类型别名)服务于相同目的时;当类型错误消息对阅读它们的人变得难以理解时。可读性原则:如果你的条件类型需要注释来解释它做什么,考虑更简单的近似(即使稍微不那么精确)是否会更好地服务于代码库。

上一篇 TypeScript Advanced Patterns: Conditional Types and Template Literals
下一篇 Korean Fermented Foods: Kimchi, Doenjang, and the Science of Jeot