Итак, когда я попытался написать аналогичный тип глубокой индексации с использованием той же неподдерживаемой рекурсии, что и в связанном вопросе, я также продолжал сталкиваться либо с предупреждениями компилятора, либо с замедлением. Это лишь одна из проблем, когда компилятор заставляет делать то, для чего он не предназначен. Возможно, однажды появится безопасное, простое и поддерживаемое решение, но сейчас его нет. См. 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
Так что я сдаюсь. Не стесняйтесь работать над этим, если осмелитесь. Все это усилие действительно подталкивает вещи к уровню, которому я бы не чувствовал себя комфортно подвергать кого-либо другому . Я рассматриваю это как «забавную и захватывающую задачу для компилятора», а не «код, от которого должно зависеть чье-либо существование».
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код