Typescript 如何使用";从嵌套属性检索类型;“路径元组”;

Typescript 如何使用";从嵌套属性检索类型;“路径元组”;,typescript,Typescript,考虑到在 但它只适用于根级别的路径,我担心我的typescript foo无法完成此任务。TS 4.1的更新 既然TypeScript支持和,您就可以更简单地编写DeepIndex: type DeepIndex<T, KS extends Keys, Fail = undefined> = KS extends [infer F, ...infer R] ? F extends keyof T ? R extends Keys ? DeepIndex<T[F]

考虑到在

但它只适用于根级别的路径,我担心我的typescript foo无法完成此任务。

TS 4.1的更新 既然TypeScript支持和,您就可以更简单地编写
DeepIndex

type DeepIndex<T, KS extends Keys, Fail = undefined> =
    KS extends [infer F, ...infer R] ? F extends keyof T ? R extends Keys ?
    DeepIndex<T[F], R, Fail> : Fail : Fail : T;
我将
DeepIndex
定义为接受类型
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;
因此,您可以看到
deepIndex()
采用
T
类型的
obj
KS
类型的
,并应产生
deepIndex
类型的结果。该实现使用
键。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
类型约束为来自
路径的类型,而不是输出
未定义的
。我尝试了五种不同的方法来实现这一点,其中大多数都彻底破坏了编译器。那些没有破坏编译器的代码比上面的代码更难看、更复杂,对于一个新手来说,它们确实没有给出有用的错误信息;我不久前提出的一个bug导致错误出现在
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
所以我放弃了。如果你有胆量的话,你可以继续努力。整个努力真的把事情推到了一个我不愿意让别人去做的程度。我认为这是“编译器的有趣和令人兴奋的挑战”,而不是“任何人的生计都应该依赖的代码”


好吧,希望这会有帮助;祝你好运


感谢您花时间如此详细地访问anwser。你对TypeScript编译器的见解非常有价值,也很容易理解,你可以在博客上发表。
type Tail<T> = T extends readonly any[] ?
    ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never
    : never;
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;
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);
}
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
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