假设我们想要定义一种类型来描述 CSS padding 规则,如果你了解 TypeScript 类型别名和联合类型的话,能很容易定义出 CssPadding 类型。
type CssPadding =
| "padding-left"
| "padding-right"
| "padding-top"
| "padding-bottom";
但如果我们想要继续定义一种新的类型来描述 CSS margin 规则,你是不是立马想到与定义 CssPadding 类型一样的方式。
type MarginPadding =
| "margin-left"
| "margin-right"
| "margin-top"
| "margin-bottom";
对于以上定义的两种类型来说,虽然它们都能满足我们的需求。但在定义这两种类型的过程中,仍然存在一些重复的代码。
那么如何解决这个问题呢?TypeScript 4.1 版本引入了新的模板字面量类型,具体的使用方式如下:
type Direction = "left" | "right" | "top" | "bottom";
type CssPadding = `padding-${Direction}`;
type MarginPadding = `margin-${Direction}`;
看完以上代码,是不是觉得简洁很多。与 JavaScript 中的模板字符串类似,模板字面量类型被括在反引号中,同时可以包含 ${T}
形式的占位符,其中类型变量 T 的类型可以是 string
、number
、boolean
或 bigint
类型。
模板字面量类型不仅为我们提供了连接字符串字面量的能力,而且还可以把非字符串基本类型的字面量转换为对应的字符串字面量类型。下面来看看一些具体的例子:
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 = Concat<"Hello", "World">; // 'Hello-World'
type T2 = ToString<"Hello" | 666 | true | -1234n>; // "Hello" | "666" | "true" | "-1234"
对于上述的例子来说,其实并不复杂。但现在问题来了,如果传入 EventName 或 Concat 工具类型的实际类型是联合类型的话,那么结果又会是怎样呢?接下来,我们来验证一下:
type T3 = EventName<"foo" | "bar" | "baz">;
// "fooChanged" | "barChanged" | "bazChanged"
type T4 = Concat<"top" | "bottom", "left" | "right">;
// "top-left" | "top-right" | "bottom-left" | "bottom-right"
为什么会生成这样的类型呢?这是因为对于模板字面量类型来说,当类型占位符的实际类型是联合类型(A |B |C)的话,就会被自动展开:
`[${A|B|C}]` => `[${A}]` | `[${B}]` | `[${C}]`
而对于包含多个类型占位符的情形,比如 Concat
工具类型。多个占位符中的联合类型解析为叉积:
`${A|B}-${C|D}` => `${A}-${C}` | `${A}-${D}` | `${B}-${C}` | `${B}-${D}`
在使用模板字面量类型的过程中,还可以使用 TypeScript 提供的,用于处理字符串类型的内置工具类型,比如 Uppercase、Lowercase、Capitalize 和 Uncapitalize。具体的使用方式是这样的:
type GetterName<T extends string> = `get${Capitalize<T>}`;
type Cases<T extends string> = `${Uppercase<T>} ${Lowercase<T>} ${Capitalize<T>} ${Uncapitalize<T>}`;
type T5 = GetterName<'foo'>; // "getFoo"
type T6 = Cases<'bar'>; // "BAR bar Bar bar"
其实,模板字面量类型的能力是很强大的,结合 TypeScript 的条件类型和 infer 关键字我们还可以实现类型推断。
type Direction = "left" | "right" | "top" | "bottom";
type InferRoot<T> = T extends `${infer R}${Capitalize<Direction>}` ? R : T;
type T7 = InferRoot<"marginRight">; // "margin"
type T8 = InferRoot<"paddingLeft">; // "padding"
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。