TypeScript 可扩展联合类型
问题背景
在日常开发过程中,我们经常会遇到一些类型定义,它们既有内置的可穷举的字面量值,又要支持开发者自己输入。比如 CSS 的 color 属性,既可以用 red、green 这些内置的颜色值,也可以用 #ff0000 这样的十六进制值。
传统联合类型的局限性
这种情况下,我们可能会首先想到使用 TypeScript 中的联合类型 (Union Type) 来定义这个类型:
1 | type Color = 'red' | 'green' | 'blue' | string |
但是这样定义存在一个问题:最终 Color 的类型其实是 string,编写代码时编辑器不会给出任何特定的颜色提示。
可扩展联合类型的解决方案
为了解决这个问题,我们可以使用可扩展联合类型:
1 | type Color = 'red' | 'green' | 'blue' | (string & {}) |
原理与作用
type Color = 'red' | 'green' | 'blue' | (string & {}) 这段代码创建了一个联合类型,它包含了两部分:
'red' | 'green' | 'blue':一个字面量类型,表示精确匹配字符串'red'、'green'、'blue'(string & {}):这部分是核心,它创建了一个特殊的可扩展类型
为什么 (string & {}) 是可扩展的?
在 TypeScript 中,{} 代表一个空对象类型,它可以匹配任何非 null 和 undefined 的值。当一个类型与 {} 进行交叉(&)时,结果会是原类型,但它会告诉 TypeScript 编译器这个类型可以被扩展。
所以,string & {} 的结果就是 string 类型,但它带上了一个特殊的”标记”。
最终效果
最终,type Color 的完整类型是 string,但它包含了一个额外的”字面量提示”。这意味着当你使用 Color 类型时,IDE 会优先提示 'red' 这个值,但同时允许你传入任何其他字符串。
核心优势
'red' | 'green' | 'blue' | (string & {}) 这种语法是一种巧妙的组合,它的作用是:
- 提供类型安全:它确保传入的值必须是字符串
- 改善开发者体验:它通过字面量提示,让开发者知道哪些是常用的、推荐的值
- 保持灵活性:它允许你传入任何其他字符串,而不仅仅局限于预设的几个值
应用场景
这种模式在构建组件库时尤其有用,因为它能在提供友好提示的同时,不限制用户的自定义能力。例如:
1 | // UI 组件的尺寸定义 |
通过这种方式,我们既能为常用场景提供良好的开发体验,又能保持 API 的灵活性和扩展性。