gpt4 book ai didi

TypeScript:使用数组获取深度嵌套的属性值

转载 作者:搜寻专家 更新时间:2023-10-30 20:37:51 24 4
gpt4 key购买 nike

我想声明一个函数,它可以接受一个对象和一个嵌套属性键数组,并将嵌套值的类型派生为函数的返回类型。

例如

const value = byPath({ state: State, path: ['one', 'two', 'three'] }); 
// return type == State['one']['two']['three']

const value2 = byPath({ state: State, path: ['one', 'two'] });
// return type == State['one']['two']

我能够组合的最好的是以下内容,但它比我希望的更冗长,而且我必须为每一层嵌套添加一个函数重载。

export function byPath<
K1 extends string,
R
>({ state, path }: {
state: {[P1 in K1]?: R},
path: [K1]
}): R;

export function byPath<
K1 extends string,
K2 extends string,
R
>({ state, path }: {
state: {[P1 in K1]?: {[P2 in K2]?: R}},
path: [K1, K2]
}): R;

export function byPath<
K1 extends string,
K2 extends string,
K3 extends string,
R
>({ state, path }: {
state: {[P1 in K1]?: {[P2 in K2]?: {[P3 in K3]?: R}}},
path: [K1, K2, K3]
}): R;

export function byPath<R>({ state, path }: { state: State, path: string[] }): R | undefined {
// do the actual nested property retrieval
}

有没有更简单/更好的方法来做到这一点?

最佳答案

不幸的是,TypeScript 目前不允许任意 recursive type functions ,这就是您想要遍历键列表,深入到对象类型,并得出与键列表对应的嵌套属性的类型。你可以做它的一部分,但它是一团糟。

所以你将不得不选择一些最高级别的嵌套并为此编写。这是不使用重载的函数的可能类型签名:

type IfKey<T, K> = [K] extends [keyof T] ? T[K] : T;

declare function byPath<T0,
K1 extends keyof T0 | undefined, T1 extends IfKey<T0, K1>,
K2 extends keyof T1 | undefined, T2 extends IfKey<T1, K2>,
K3 extends keyof T2 | undefined, T3 extends IfKey<T2, K3>,
K4 extends keyof T3 | undefined, T4 extends IfKey<T3, K4>,
K5 extends keyof T4 | undefined, T5 extends IfKey<T4, K5>,
K6 extends keyof T5 | undefined, T6 extends IfKey<T5, K6>
>({ state, path }: { state: T0, path: [K1?, K2?, K3?, K4?, K5?, K6?] }): T6;

请注意,如果需要,您可以轻松地将其扩展到六层以上的嵌套。

它的工作方式:有两种类型参数...键类型(名为 K1K2 等)和对象类型(名为 T0T1 等)。 state 属性的类型为 T0,路径为 tuple with optional elements的关键类型。每个键类型要么是前一个对象类型的键,要么是未定义。如果键未定义,则下一个对象类型与当前对象类型相同;否则就是相关属性的类型。因此,一旦键类型变为并保持 undefined,对象类型将变为并保持最后一个相关属性类型......最后一个对象类型(上面的 T6)是函数的返回类型。

举个例子:如果T0{a: {b: string}, c: {d: string}},那么K1 必须是 'a''d'undefined 之一。假设 K1'a'。那么T1就是{b:string}。现在 K2 必须是 'b'undefined。假设 K2'b'。那么T2就是string。现在 K3 必须在 keyof stringundefined 中。 (因此 K3 可以是 "charAt",或任何 string 方法和属性)。假设 K3undefined。然后 T3string(因为它与 T2 相同)。如果 K4K5K6 的所有其余部分都是 undefined,则 T4T5T6 只是string。函数返回 T6

因此,如果您执行此调用:

const ret = byPath({state: {a: {b: "hey"}, c: {d: "you"} }, path: ['a', 'b'] });

那么T0会被推断为{a: {b: string}, c: {d: string}, K1会是'a'K2 将是 'b'K3K6都将是 undefined。这是上面的例子,所以 T6 将是 string。因此 ret 将是 string 类型。

如果你输入了错误的 key ,上面的函数签名也会对你大喊大叫:

const whoops = byPath({ state: { a: { b: "hey" }, c: { d: "you" } }, path: ['a', 'B'] });
// error! type "B" is not assignable to "b" | undefined: ----------------------> ~~~

该错误是有道理的,因为 B 无效。下面也骂你:

const alsoWhoops = byPath({ state: { a: { b: "hey" }, c: { d: "you" } }, path: ['A', 'b'] });
// error! type "A" is not assignable to "a" | "c" | undefined: ---------------> ~~~
// also error! Type "b" is not assignable to "a" | "c" | undefined ?! -------------> ~~~

第一个错误正是您所期望的;第二个有点奇怪,因为 "b" 没问题。但是编译器现在不知道 keyof T['A'] 会发生什么,所以它的行为就好像 K1undefined 一样。如果您修复了第一个错误,第二个错误就会消失。可能有一些方法可以改变 byPath() 签名来避免这种情况,但这对我来说似乎微不足道。


无论如何,希望对您有所帮助或给您一些想法。祝你好运!


编辑:如果您关心错误的第二条错误消息,您可以使用稍微复杂一点的:

type IfKey<T, K> = [K] extends [keyof T] ? T[K] : T
type NextKey<T, K = keyof any> = [K] extends [undefined] ? undefined :
[keyof T | undefined] extends [K] ? keyof any : (keyof T | undefined)

declare function byPath<T0,
K1 extends NextKey<T0>, T1 extends IfKey<T0, K1>,
K2 extends NextKey<T1, K1>, T2 extends IfKey<T1, K2>,
K3 extends NextKey<T2, K2>, T3 extends IfKey<T2, K3>,
K4 extends NextKey<T3, K3>, T4 extends IfKey<T3, K4>,
K5 extends NextKey<T4, K4>, T5 extends IfKey<T4, K5>,
K6 extends NextKey<T5, K5>, T6 extends IfKey<T5, K6>
>({ state, path }: { state: T0, path: [K1?, K2?, K3?, K4?, K5?, K6?] }): T6;

这几乎是一样的,除了当键与它们应该匹配的不匹配时出现问题。

关于TypeScript:使用数组获取深度嵌套的属性值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53293200/

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