Литеральный тип шаблона TypeScript — как определить числовой тип?

avatar
burtek
7 сентября 2021 в 13:56
3357
2
8
// from a library
type T = null | "auto" | "text0" | "text1" | "text2" | "text3" | "text4";

//in my code
type N = Extract<T, `text${number}`> extends `text${infer R}` ? R : never

(Игровая площадка ТП)

Для приведенного выше фрагмента кода N будет эквивалентно "0" | "1" | "2" | "3" | "4". Как я могу преобразовать это в числовой тип, то есть 0 | 1 | 2 | 3 | 4? Уже пробовал вставлять & number в некоторых местах, например infer R & number, но ничего из этого не работает.

Источник
captain-yossarian
7 сентября 2021 в 14:28
1

На данный момент это кажется невозможным coderhelper.com/questions/65410801/…

captain-yossarian
7 сентября 2021 в 14:29
0

Вы можете создать структуру данных хэш-карты и карту из "1" -> 1, но она не является универсальной и работает только для 10 цифр.

burtek
7 сентября 2021 в 14:31
0

@captain-yossarian TBH Я предпочитаю просто создавать type N = 0 | 1 | 2 | 3 | 4 вручную, пока не будет доступно встроенное решение TS. Хотя спасибо за идею

Ответы (2)

avatar
captain-yossarian
7 сентября 2021 в 14:38
16

ОБНОВЛЕНИЕ

type MAXIMUM_ALLOWED_BOUNDARY = 999

type Mapped<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result
        : Mapped<N, [...Result, Result['length']]>
    )


type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number] // 0.. 998


type ConvertToNumber<T extends string, Range extends number> =
    (Range extends any
        ? (`${Range}` extends T
            ? Range
            : never)
        : never)

type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125

Детская площадка

П.С. извините за название, я в этом не силен.

Кажется, в настоящее время это невозможно, но есть обходной путь.

Вы можете создать Dictionary для номеров в диапазоне 0..42:

// from a library
type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never

type T = null | "auto" | Texts<Enumerate<43>>;

type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;

type EnumerateInternal<A extends Array<unknown>  N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>  N> }[N extends A['length'] ? 0 : 1];

type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;

type Dictionary = {
    [Prop in Enumerate<43> as `${Prop}`]: Prop
}

//  0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42
type N =
    Extract<T, `text${number}`> extends `text${infer R}`
    ? R extends keyof Dictionary
    ? Dictionary[R]
    : never
    : never

После того, как хвостовая рекурсия PR будет объединена

, можно будет сгенерировать гораздо больший диапазон.

Детская площадка

ОБНОВЛЕНИЕ - как я и обещал

Попробовать

type MAXIMUM_ALLOWED_BOUNDARY = 999

type Mapped<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result
        : Mapped<N, [...Result, Result['length']]>
    )

// 0 , 1, 2 ... 998
type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>[number]


type Texts<T extends PropertyKey> = T extends number ? `text${T}` : never


type T = null | "auto" | Texts<NumberRange>;

type Dictionary = {
    [Prop in NumberRange as `${Prop}`]: Prop
}

//  0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 ... 998
type N =
    Extract<T, `text${number}`> extends `text${infer R}`
    ? R extends keyof Dictionary
    ? Dictionary[R]
    : never
    : never

Детская площадка

Вы можете попробовать указанное выше решение на игровой площадке TS с версией TS 4.5 (ночная) Код намного проще.

Здесь у вас есть javascript-представление Mapped:

const Mapped = (N: number, Result: number[] = []): number[] => {
    if (Result.length === N) {
        return Result
    }
    return Mapped(N, [...Result, Result.length])
}

Ничего сложного. Хвостовая рекурсия.


spender
7 сентября 2021 в 14:56
1

Ох. Новый пиар от самого босса. TailRec в системе типов был бы отличным дополнением :)

captain-yossarian
7 сентября 2021 в 14:58
0

@spender правда. У меня уже есть много ответов, которые нужно улучшить ))

burtek
8 сентября 2021 в 15:25
1

Мне понадобится секунда, чтобы понять, что здесь происходит, но это может быть излишним для того, что мне нужно. Хотя за эту идею спасибо

captain-yossarian
8 сентября 2021 в 15:26
0

вечером дополню описание

avatar
Trevor Manz
11 декабря 2021 в 16:48
0

Спасибо за ответ, @captain-yossarian. Я обнаружил, что вы также можете сохранить кортеж Mapped<MAXIMUM_ALLOWED_BOUNDARY> и индексировать, используя строковый индекс. Конечно, Range должен быть кортежем.

type MAXIMUM_ALLOWED_BOUNDARY = 999

type Mapped<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result
        : Mapped<N, [...Result, Result['length']]>
    )


type NumberRange = Mapped<MAXIMUM_ALLOWED_BOUNDARY>; // <- tuple [0, 1, 2, 3, ...]


type ConvertToNumber<T extends string, Range extends number[]> = 
  T extends keyof Range ? Range[T] : never;

type _ = ConvertToNumber<'5', NumberRange> // 5
type __ = ConvertToNumber<'125', NumberRange> // 125

Детская площадка