gpt4 book ai didi

typescript - 如何使用 "path tuple"从嵌套属性中检索类型

转载 作者:行者123 更新时间:2023-12-05 00:56:45 26 4
gpt4 key购买 nike

鉴于 Typescript: deep keyof of a nested object 中的这段(惊人的)代码

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>> = ...

问题在于编译器对此签名不满意:类型实例化过深并且可能无限

我找到了一种通过缩小 T 来消除此错误的方法:

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

但它只适用于根级别的路径,我担心我的 typescript-foo 不能胜任这项任务。

最佳答案

TS 4.1 更新

现在 TypeScript 支持 recursive conditional typesvariadic tuple types ,你可以写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;

这可能仍然对树状类型有一些“有趣”的行为,但是自从我在下面写下答案后,情况肯定有所改善:

Playground link to code .


因此,当我尝试使用与链接问题中相同的不受支持的递归来编写类似的深度索引类型时,我也不断遇到编译器警告或减速。这只是插入编译器做它不应该做的事情的问题之一。也许有一天会出现一种安全、简单且受支持的解决方案,但现在还没有。见 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 type 是几乎递归的,但它不是引用自身,而是引用另一个几乎相同的类型,最终摆脱了 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类型 Tkeys类型 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 .我尝试了大约五种不同的方法来做到这一点,其中大多数完全破坏了编译器。那些没有让编译器崩溃的比上面的更丑更复杂,而且对于一个踢球者来说,他们真的没有给出有用的错误信息;我不久前提交了一个问题的错误,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

所以我放弃了。如果你敢的话,请随意做更多的工作。整个努力确实将事情推向了一个我不会让任何人其他服从的水平。我认为这是“对编译器来说既有趣又令人兴奋的挑战”,而不是“任何人的生计都应该依赖的代码”。


好的,希望对您有所帮助;祝你好运!

Playground link to code

关于typescript - 如何使用 "path tuple"从嵌套属性中检索类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61644053/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com