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

Posted

技术标签:

【中文标题】如何键入 Typescript 数组以仅接受一组特定的值?【英文标题】:How to type a Typescript array to accept only a specific set of values? 【发布时间】:2019-10-13 04:57:19 【问题描述】:

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

【问题讨论】:

How to require a specific string in TypeScript interface的可能重复 为了简短地回答您的问题,鉴于当前的 TS 功能,您已经有了最佳解决方案。 @Nit 不是重复的,因为这是专门讨论限制数组中的值,而不是该问题所询问的字符串字段。 问题和答案是一样的,一个是直接输入foo,另一个是输入foo[] 我承认,鉴于 TypeScript 的当前功能,解决方案可能是相同的,但问题的主题是根本不同的。也就是说,解决方案仍然没有报告正确的错误,导致我在问题底部的最终查询。 【参考方案1】:

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

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 并将元组类型扩大到数组,除非你提示它不要这样做。因此,当它无法验证fruitsFruit[] 类型时,它会备份并查看您给它的内容。它将["Apple", "App1e", "Pear"] 扩大到string[](忘记了字符串文字和它是一个三元素元组的事实),意识到string[] 不能分配给Fruit[],然后继续警告你这个通过标记每个元素。我对GitHub issues 做了一个简短的搜索,看看是否有人报告过这件事,但我还没有看到。可能值得提交一些东西。

无论如何,为了测试我的猜测,我决定更改Fruits() 的声明,以暗示我们想要一个字符串字面量的元组,如果可能的话。注意there is currently no convenient way to do this;现在进行暗示的方法是,呃,炼金术:

// ?⚗??❓
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 &amp; "App1e" 时发生的情况。编译器将Fruit &amp; "App1e" 正确地减少为never...,但对于错误消息来说可能有点太早了。

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

Link to code

【讨论】:

【参考方案2】:

您也可以为此使用枚举:

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

【讨论】:

【参考方案3】:

如果你不想要一个类型,它也可以工作

export interface MyInterface 
   fruits: Array<'apple' | 'pear' | 'strawberry'>

【讨论】:

以上是关于如何键入 Typescript 数组以仅接受一组特定的值?的主要内容,如果未能解决你的问题,请参考以下文章

键入一组显式值类型的数组[重复]

TypeScript:如何正确键入一个接受承诺并按原样返回该承诺的函数

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何使用 Typescript 在 mongoose 中正确键入对象 ID 数组

如何在 TypeScript 中键入带有类的数组?

如何将联合类型指定为对象键 Typescript