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

Sha*_*man 12 arrays type-declaration typescript union-types

我正在为我无法控制的库编写类型声明文件。其中一个方法接受一个字符串数组作为参数,但这些字符串只能是非常具体的值。目前我正在将此参数输入为 a 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;
}
Run Code Online (Sandbox Code Playgroud)

我当前的类型声明:

function Fruits(filter: FruitFilter): boolean;

interface FruitFilter {
    fruits: string[];
}
Run Code Online (Sandbox Code Playgroud)

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

部分解决方案:

function Fruits(filter: FruitFilter): boolean;

type Fruit = 'Apple' | 'Pear'

interface FruitFilter {
    fruits: Fruit[];
}
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 17

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

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
Run Code Online (Sandbox Code Playgroud)

并不是你有错误,而是错误没有正确地限制在数组的“坏”元素上。

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

无论如何,为了测试我的猜测,我决定更改 的声明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
Run Code Online (Sandbox Code Playgroud)

好吧,该错误的位置就是您想要的位置,尽管该消息可能仍然令人困惑。当编译器尝试分配"Apple"Fruit & "App1e"不存在的交集时,就会发生这种情况。编译器正确地减少Fruit & "App1e"never...,但可能有点太快了,错误消息是有用的。

无论如何,我不建议这样做的“解决方案”,因为它的复杂得多,只给你一个有点错误的情况下更好的错误经验。但至少这是关于它为什么发生的答案,以及如何解决它的可能方向(例如,查找或提交有关它的问题)。好的,祝你好运!

代码链接


acm*_*cme 6

您还可以使用枚举来实现:

enum Fruits {
    Apple,
    Pear,
}

interface FruitFilter {
    fruits: Array<Fruits>;
}
Run Code Online (Sandbox Code Playgroud)

这些将在纯 JavaScript 中转换为 0 和 1。

如果需要,您还可以使用字符串代替数字。然后你必须像这样定义枚举:

enum Fruits {
    Apple = 'Apple',
    Pear = 'Pear',
}
Run Code Online (Sandbox Code Playgroud)

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

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


小智 6

如果您不需要类型,它也可以工作

export interface MyInterface {
   fruits: Array<'apple' | 'pear' | 'strawberry'>
}
Run Code Online (Sandbox Code Playgroud)