我正在创建一个对象来存储一堆RGB颜色,并且允许嵌套。因此,当循环遍历对象时,我需要查看哪些关键点对应于RGB值或对象。然而,我尝试过的每一种类型的后卫实际上都不会缩小类型。
type Color = [number, number, number] | 'transparent'
type ColorGroup = Record<string, Color>
type Colors = Record<string, Color | ColorGroup>
const colors: Colors = {
black: [0, 0, 0],
white: [255, 255, 255],
transparent: 'transparent',
primary: {
'50': [211, 233, 252],
'100': [179, 213, 248],
'200': [127, 185, 251],
'300': [68, 156, 253],
'400': [0, 126, 254],
'500': [13, 100, 226],
'600': [17, 79, 189],
'700': [15, 62, 157],
'800': [10, 46, 122],
'900': [1, 22, 77],
}
}
const isColor = (color: Color | ColorGroup): color is Color => {
return Array.isArray(color) || typeof color === 'string'
}
const usesColor = (color: Color):void => {
// does something with the color
}
for(const color in colors) {
if(isColor(colors[color])) usesColor(colors[color]) // error: type 'Record<string, Color>' is not assignable to type 'Color'
}
游乐场链接
有什么想法吗?我是不是错过了一些关于类型后卫的基本知识?
您在TypeScript中遇到了设计限制。有关详细信息,请参阅microsoft/TypeScript#33391和microsoft/TypeScript#3145。
问题是编译器不会跟踪属性类型保护的结果,除非这些属性是字符串或数字:
if (isColor(colors.black)) usesColor(colors.black); // okay
如果是存储在变量中的值,则不是:
if (isColor(colors[color])) usesColor(colors[color]) // error!
当访问colors[color]
时,编译器只知道color
是string
类型的变量。在类型保护之后,您再次访问colors[color]
,但编译器没有意识到您之前检查过它,因为color
只是string
类型的变量
declare const color1: string;
declare const color2: string;
if (isColor(colors[color1])) usesColor(colors[color2]); // error!
这将不是该类型防护装置的良好使用。
上述相关问题提到,虽然支持这样的代码会很好,但就编译器资源而言,这是非常昂贵的。跟踪哪些变量被用作索引是一项额外的工作,而且几乎总是不必要的。这里的用例显然不值得…尤其是因为:
有一个小的重构,它提供了您想要的行为。与其进行多个索引操作,不如进行一个索引操作并将其保存到自己的变量中,如下所示:
for (const color in colors) {
const c = colors[color];
if (isColor(c)) usesColor(c) // okay
}
由于c
是它自己的变量,因此不再需要担心使用-string
进行索引。编译器可以很容易地在c
上使用类型保护来缩小c
的类型。因此,您可以获得所需的行为,而牺牲了稍微不那么惯用的JavaScript。
游乐场链接到代码