首页 > 解决方案 > Typeguard 不会缩小类型

问题描述

我正在创建一个对象来存储一堆 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'
}

游乐场链接

有任何想法吗?我只是错过了一些关于类型保护的基本知识吗?

标签: typescripttypeguards

解决方案


您在 TypeScript 中遇到了设计限制。有关详细信息,请参阅microsoft/TypeScript#33391microsoft/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-typed 变量。从某种意义上说,编译器看不到您的代码与以下代码之间的区别:

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。

Playground 代码链接


推荐阅读