Использовать строковый литерал в качестве типа - PullRequest
0 голосов
/ 05 ноября 2019

Я хочу использовать один массив const fruits = ['banana', 'apple', 'orange'] как обычный массив и как type.

Я должен быть в состоянии сделать это: const x: fruits // => only accepts 'banana', 'apple' or 'orange'

А такжебыть в состоянии сделать это: @IsIn(fruits)


Я пытался объявить массив как <const>, например:

const fruits = <const>['banana', 'apple', 'orange']
type Fruits = typeof fruits[number] // this evaluates to type: "banana" | "apple" | "orange"

Но @IsIn(fruits) вернетследующая ошибка:

Argument of type 'readonly ["banana", "apple", "orange"]' is not assignable to parameter of type 'any[]'.
  The type 'readonly ["banana", "apple", "orange"]' is 'readonly' and cannot be assigned to the mutable type 'any[]'.ts(2345)

Поэтому я подумал, что если я создам два массива, обычный и только для чтения, он должен работать. Итак, я попробовал это:

const fruits = ['banana', 'apple', 'orange']
const fruits_readonly: <const>[...fruits]
type Fruits = typeof fruits_readonly[number]

Но теперь Fruits равно type: string вместо type: "banana" | "apple" | "orange".

1 Ответ

1 голос
/ 05 ноября 2019

Это правда, что const утверждения создают объекты и массивы с readonly элементами. Если вы хотите получить преимущества типа string-literal и tuple, которые дает вам утверждение const, в то же время не обрабатывая результат, вы можете написать вспомогательную функцию для этого. Я назову это mutable():

const mutable = <T>(t: T): { -readonly [K in keyof T]: T[K] } => t

const fruits = mutable(['banana', 'apple', 'orange'] as const);
// const fruits: ["banana", "apple", "orange"]

Это будет работать на один уровень глубже. Если у вас есть вложенные типы объектов / массивов, возможно, вы захотите создать вспомогательную функцию типа DeepMutable и deepMutable():

type DeepMutable<T> =
    T extends object ? { -readonly [K in keyof T]: DeepMutable<T[K]> } : T

const deepMutable = <T>(t: T) => t as DeepMutable<T>;

, которая работает так же для приведенного выше случая,

const alsoFruits = deepMutable(['banana', 'apple', 'orange'] as const);
// const alsoFruits: ["banana", "apple", "orange"]

но различие становится важным для вложенных объектов:

const readonlyDeepFruits = {
    yellow: ["banana", "lemon"],
    red: ["cherry", "apple"],
    orange: ["orange", "mango"],
    green: ["lime", "watermelon"]
} as const;
/* const readonlyDeepFruits: {
    readonly yellow: readonly ["banana", "lemon"];
    readonly red: readonly ["cherry", "apple"];
    readonly orange: readonly ["orange", "mango"];
    readonly green: readonly ["lime", "watermelon"];
} */

const partiallyMutableDeepFruits = mutable(readonlyDeepFruits);
/* const partiallyMutableDeepFruits: {
    yellow: readonly ["banana", "lemon"];
    red: readonly ["cherry", "apple"];
    orange: readonly ["orange", "mango"];
    green: readonly ["lime", "watermelon"];
} */

const fullyMutableDeepFruits = deepMutable(readonlyDeepFruits);
/* const fullyMutableDeepFruits: {
    yellow: ["banana", "lemon"];
    red: ["cherry", "apple"];
    orange: ["orange", "mango"];
    green: ["lime", "watermelon"];
} */

Хорошо, надеюсь, это поможет. Удачи!

Ссылка на код

...