问题描述
我正在创建一个对象来存储一堆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],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#31445。
问题在于,除非这些属性是字符串文字或数字文字,否则编译器不会跟踪属性类型保护的结果:
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。