如何键入Typescript数组以仅接受一组特定的值?

时间:2019-05-27 15:43:06

标签: arrays typescript union-types type-declaration

我正在为我不控制的库编写类型声明文件。其中一种方法接受字符串数组作为参数,但是这些字符串只能是非常特定的值。目前,我正在以string[]形式输入此参数,但我想知道是否有一种方法可以增强此参数以使其包括特定值。

示例来源(我无法更改):

Fruits(filter) {
    for (let fruit of filter.fruits)
    {
        switch(fruit)
        {
            case 'Apple':
                ...do stuff
            case 'Pear':
                ...do stuff
            default:
                console.error('Invalid Fruit');
                return false;
        }
    }
    return true;
}

我当前的类型声明:

function Fruits(filter: FruitFilter): boolean;

interface FruitFilter {
    fruits: string[];
}

在撰写此问题时,我提出了一个部分解决方案,方法是定义有效字符串的联合类型,然后将字段类型设置为该联合的数组而不是字符串数组。这给了我想要的检查,但是我注意到,如果输入无效的字符串,则会将数组中的所有字符串标记为无效,并显示错误Type 'string' is not assignable to type 'Fruit'。有没有更好的方法可以做到,这样仅将有问题的字符串标记为无效字符串,或者将其标记为接近我要得到的字符串?

部分解决方案:

function Fruits(filter: FruitFilter): boolean;

type Fruit = 'Apple' | 'Pear'

interface FruitFilter {
    fruits: Fruit[];
}

2 个答案:

答案 0 :(得分:3)

所以,您的问题似乎是这样的:

type Fruit = "Apple" | "Pear";
interface FruitFilter {
  fruits: Fruit[];
}
declare function Fruits(filter: FruitFilter): boolean;
Fruits({ fruits: ["Apple", "Apple", "Pear"] }); // okay
Fruits({ fruits: ["Apple", "App1e", "Pear"] }); // error
// actual error: ~~~~~~~  ~~~~~~~  ~~~~~~ <-- string not assignable to Fruit
// expected error:        ~~~~~~~ <-- "App1e" not assignable to Fruit

不是您有一个错误,而是错误没有适当地限制在数组的“坏”元素上。

我对发生这种情况的猜测是,编译器倾向于将字符串文字扩展为string,将元组类型扩展为数组,除非您给出提示不要这样做。因此,当无法验证fruits的类型为Fruit[]时,它将备份并查看您提供的内容。它将["Apple", "App1e", "Pear"]扩展为string[](忘记了字符串文字和它是一个三元素元组的事实),意识到string[]不可分配给Fruit[],然后通过标记每个元素来警告您。我对GitHub issues进行了简短搜索,以查看是否有过报告,但是我还没有看到。可能值得提出一些东西。

无论如何,为了检验我的猜测,我决定更改Fruits()的声明,以暗示我们尽可能要一个字符串文字元组。请注意,[目前尚无方便的方法];现在提示的方法是炼金术:

// ⚗❓
declare function Fruits2<S extends string, T extends S[] | [S]>(arr: {
  fruits: T & { [K in keyof T]: Fruit };
}): boolean;
Fruits2({ fruits: ["Apple", "Apple", "Pear"] }); // okay
Fruits2({ fruits: ["Apple", "App1e", "Pear"] }); // error
//                          ~~~~~~~ <--string is not assignable to never

好吧,尽管该消息可能仍然令人困惑,但该错误的位置仍然是您想要的位置。当编译器尝试将"Apple"分配给不存在的交点Fruit & "App1e"时,就会发生这种情况。编译器正确地将Fruit & "App1e"减小为never ...,但可能为时过早,以致错误消息不起作用。

无论如何,我不推荐这种“解决方案”,因为它要复杂得多,并且在错误情况下只能为您提供更好的错误体验。但这至少是关于发生原因的答案,以及解决该问题的可能方向(例如查找或提出问题的可能方向)。好的,祝你好运!

Link to code

答案 1 :(得分:1)

您也可以为此使用枚举:

enum Fruits {
    Apple,
    Pear,
}

interface FruitFilter {
    fruits: Array<Fruits>;
}

这些将用纯Javascript转换为0和1。

如果需要,您也可以使用字符串而不是数字。然后,您必须像这样定义枚举:

enum Fruits {
    Apple = 'Apple',
    Pear = 'Pear',
}

TypeScript文档还有更多示例,以及如何在运行时使用它:

https://www.typescriptlang.org/docs/handbook/enums.html#enums-at-runtime