Как получить тип из вложенного свойства с помощью «кортежа пути» - PullRequest
0 голосов
/ 06 мая 2020

Учитывая этот (удивительный) фрагмент кода, найденный в Typescript: глубокий ключ вложенного объекта

type Cons<H, T> = T extends readonly any[] ?
    ((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never
    : never;

type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]

type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
    { [K in keyof T]-?: [K] | (Paths<T[K], Prev[D]> extends infer P ?
        P extends [] ? never : Cons<K, P> : never
    ) }[keyof T]
    : [];

, который помогает нам получить вложенные пути объекта как объединение кортежи, например:

type Obj = {
  A: { a1: string }
  B: { b1: string, b2: { b2a: string } }
}

type ObjPaths = Paths<obj> // ['A'] | ['A', 'a1'] | ['B'] | ['B', 'b1'] | ['B', 'b2'] | ['B', 'b2', 'b2a']

Я ищу «обратный» способ получить тип из вложенного свойства, используя кортеж пути в форме:

type TypeAtPath<T extends object, U extends Paths<T>> = ...

Проблема в том, что компилятору не нравится эта сигнатура: Type instantiation is excessively deep and possibly infinite.

Я нашел способ убрать эту ошибку, сузив T:

type TypeAtPath<T extends {[key: string]: any}, U extends Paths<T>> = T[U[0]]

Но это работает только для путей на уровне root, и я боюсь, что мой typescript-foo не подходит для этой задачи.

1 Ответ

1 голос
/ 07 мая 2020

Итак, когда я попытался написать аналогичный тип глубокой индексации с использованием той же неподдерживаемой рекурсии, что и в связанном вопросе, я также продолжал сталкиваться либо с предупреждениями компилятора, либо с замедлением. Это лишь одна из проблем, когда компилятор заставляет делать то, для чего он не предназначен. Возможно, однажды появится безопасное, простое и поддерживаемое решение, но сейчас его нет. См. microsoft / TypeScript # 26980 для обсуждения поддержки циклических условных типов.

Сейчас я собираюсь сделать свою старую резервную копию для написания рекурсивных условных типов: возьмите предполагаемое рекурсивный тип и развернуть его в серию нерекурсивных типов, которые явно выскакивают на некоторой глубине:

Учитывая Tail<T>, который принимает тип кортежа, например [1,2,3], и удаляет первый элемент для создания меньшего кортежа например, [2, 3]:

type Tail<T> = T extends readonly any[] ?
    ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never
    : never;

Я определю DeepIndex<T, KS, F> как что-то, что принимает тип T и кортеж типов ключей KS и переходит в T с этими ключи, производящие тип найденного там вложенного свойства. Если это заканчивается попыткой индексировать что-то с ключом, которого у него нет, это приведет к ошибке типа F, который по умолчанию должен быть примерно таким: undefined:

type Keys = readonly PropertyKey[];
type DeepIndex<T, KS extends Keys, F = undefined> = Idx0<T, KS, F>;
type Idx0<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx1<T[KS[0]], Tail<KS>, F> : F;
type Idx1<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx2<T[KS[0]], Tail<KS>, F> : F;
type Idx2<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx3<T[KS[0]], Tail<KS>, F> : F;
type Idx3<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx4<T[KS[0]], Tail<KS>, F> : F;
type Idx4<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx5<T[KS[0]], Tail<KS>, F> : F;
type Idx5<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx6<T[KS[0]], Tail<KS>, F> : F;
type Idx6<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx7<T[KS[0]], Tail<KS>, F> : F;
type Idx7<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx8<T[KS[0]], Tail<KS>, F> : F;
type Idx8<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? Idx9<T[KS[0]], Tail<KS>, F> : F;
type Idx9<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? IdxX<T[KS[0]], Tail<KS>, F> : F;
type IdxX<T, KS extends Keys, F> = KS['length'] extends 0 ? T : KS[0] extends keyof T ? T[KS[0]] : F;

Здесь вы можете посмотрите, как тип Idx является почти рекурсивным, но вместо того, чтобы ссылаться на себя, он ссылается на другой почти идентичный тип, что в конечном итоге приводит к снижению глубины на 10 уровней.


Я бы мог представить, как это использовать:

function deepIndex<T, KS extends Keys, K extends PropertyKey>(
  obj: T, 
  ...keys: KS & K[]
): DeepIndex<T, KS>;
function deepIndex(obj: any, ...keys: Keys) {
    return keys.reduce((o, k) => o?.[k], obj);
}

Итак, вы можете видеть, что deepIndex() принимает obj типа T и keys типа KS и должен производить результат типа DeepIndex<T, KS>. В реализации используется keys.reduce(). Посмотрим, работает ли:

const obj = {
    a: { b: { c: 1 }, d: { e: "" } },
    f: { g: { h: { i: true } } }, j: { k: [{ l: "hey" }] }
}

const c = deepIndex(obj, "a", "b", "c"); // number 
const e = deepIndex(obj, "a", "d", "e"); // string
const i = deepIndex(obj, "f", "g", "h", "i"); // boolean
const l = deepIndex(obj, "j", "k", 0, "l"); // string
const oops = deepIndex(obj, "a", "b", "c", "d"); // undefined
const hmm = deepIndex(obj, "a", "b", "c", "toFixed"); // (fractionDigits?: number) => string

Мне нравится.


Обратите внимание, что я уверен, что вы хотели бы иметь функцию deepIndex() или тип DeepIndex на самом деле ограничивать тип KS типами из Paths<T> вместо вывода undefined. Я пробовал пять разных способов сделать это, и большинство из них полностью уничтожили компилятор. А те, которые не сбивали компилятор, были уродливее и сложнее, чем вышеперечисленные, и для кикера они действительно не выдавали полезных сообщений об ошибках; ошибка Какое-то время я сообщал о проблеме go, microsoft / TypeScript # 28505 , вызывает появление ошибки в неправильном элементе массива keys. Итак, вы хотели бы увидеть

const oops = deepIndex(obj, "a", "b", "c", "d"); // error!
// --------------------------------------> ~~~
// "d" is not assignable to keyof number

, но на самом деле произойдет

const oops = deepIndex(obj, "a", "b", "c", "d"); // error!
// -----------------------> ~~~
// "d" is not assignable to never

Так что я сдаюсь. Не стесняйтесь работать над этим, если осмелитесь. Все это усилие действительно подталкивает вещи к уровню, которому я бы не чувствовал себя комфортно подвергать кого-либо другому . Я рассматриваю это как «забавную и захватывающую задачу для компилятора», а не «код, от которого должно зависеть чье-либо существование».


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

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

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