TypeScript:使用数组获取深度嵌套的属性值 [英] TypeScript: Get deeply nested property value using array

查看:36
本文介绍了TypeScript:使用数组获取深度嵌套的属性值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'd like to declare a function that can take an object plus an array of nested property keys and derive the type of the nested value as the return type of the function.

例如

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']

我能整理出的最好的内容如下,但它比我希望的要冗长,而且我必须为每一级嵌套添加一个函数重载.

The best I've been able to put together is the following, but it is more verbose than I'd like it to be, and I have to add a function overload for every level of nesting.

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
}

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

Is there a simpler / better way to do this?

推荐答案

不幸的是,TypeScript 目前不允许任意 递归类型函数,就是你要遍历一个键列表,深入到一个对象类型,并得出与键列表对应的嵌套属性的类型.你可以做它的一部分,但它是一团糟.

Unfortunately, TypeScript doesn't currently allow arbitrary recursive type functions, which is what you want to iterate through a list of keys, drill down into an object type, and come out with the type of the nested property corresponding to the list of keys. You can do pieces of it, but it's a mess.

因此,您将不得不选择一些最高级别的嵌套并为此编写代码.这是不使用重载的函数的可能类型签名:

So you're going to have to pick some maximum level of nesting and write for that. Here's a possible type signature for your function which doesn't use overloads:

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;

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

Note that you can easily extend that to more than six layers of nesting if you need to.

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

The way it works: there are two kinds of type parameters... key types (named K1, K2, etc), and object types (named T0, T1, etc). The state property is of type T0, and the path is a tuple with optional elements of the key types. Each key type is either a key of the previous object type, or it's undefined. If the key is undefined, then the next object type is the same as the current object type; otherwise it's the type of the relevant property. So as soon as key types become and stay undefined, the object types become and stay the last relevant property type... and the last object type (T6 above) is the return type of the function.

举个例子:如果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.那么T3就是string(因为它和T2是一样的).如果 K4K5K6 的其余部分都是 undefined,则 T4T5T6 只是 string.并且函数返回T6.

Let's do an example: if T0 is {a: {b: string}, c: {d: string}}, then K1 must be one of 'a', 'd', or undefined. Let's say that K1 is 'a'. Then T1 is {b: string}. Now K2 must be 'b' or undefined. Let's say that K2 is 'b'. Then T2 is string. Now K3 must be in keyof string or undefined. (So K3 could be "charAt", or any of the string methods and properties). Let's say that K3 is undefined. Then T3 is string (since it is the same as T2). And if all the rest of K4, K5, and K6 are undefined, then T4, T5, and T6 are just string. And the function returns T6.

因此,如果您拨打此电话:

So if you do this call:

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 类型.

Then T0 will be inferred as {a: {b: string}, c: {d: string}, K1 will be 'a', K2 will be 'b', and K3 through K6 will all be undefined. Which is the example above, so T6 will be string. And thus ret will of type string.

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

The above function signature should also yell at you if you enter a bad key:

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

该错误是有道理的,因为 B 无效.以下内容也会对您大吼:

That error makes sense, since B is not valid. The following also yells at you:

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() 签名来避免这种情况,但对我来说似乎微不足道.

The first error is exactly what you'd expect; the second is a little weird, since "b" is fine. But the compiler now has no idea what to expect for keyof T['A'], so it is acting as if K1 were undefined. If you fix the first error, the second will go away. There might be ways to alter the byPath() signature to avoid this, but it seems minor to me.

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

Anyway, hope that helps you or gives you some ideas. Good luck!

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

in case you care about that erroneous second error message you could use the slightly more complex:

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;

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

which is pretty much the same except for when things go wrong with keys not matching what they're supposed to match.

这篇关于TypeScript:使用数组获取深度嵌套的属性值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆