Как набрать массив Typescript для принятия только определенного набора значений? - PullRequest
1 голос
/ 27 мая 2019

Я пишу файл объявления типа для библиотеки, которую я не контролирую.Один из методов принимает массив строк в качестве параметра, но эти строки могут быть только очень конкретными значениями.В настоящее время я печатаю этот параметр как 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[];
}

1 Ответ

3 голосов
/ 27 мая 2019

Итак, ваша проблема выглядит так:

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 , чтобы узнать, было ли об этом когда-либо сообщено, но я этого не видел. Возможно, стоит что-то подать.

В любом случае, чтобы проверить свои предположения, я решил изменить объявление 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 ..., но, возможно, слишком рано, чтобы сообщение об ошибке было полезным.

В любом случае, я не рекомендую это «решение», так как оно намного сложнее и дает вам несколько лучший опыт ошибок в ситуациях с ошибками. Но, по крайней мере, это что-то вроде ответа о том, почему это происходит, вместе с возможным указанием того, как его решить (например, найти или подать вопрос об этом). Хорошо, удачи!

Ссылка на код

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...