不幸的是,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;
请注意,如果需要,您可以轻松地将其扩展到六层以上的嵌套。
它的工作方式:有两种类型参数...键类型(命名为K1、K2 等)和对象类型(命名为T0、T1 等)。 state 属性的类型是 T0,路径是键类型的 tuple with optional elements。每个键类型要么是前一个对象类型的键,要么是 undefined。如果键未定义,则下一个对象类型与当前对象类型相同;否则它是相关属性的类型。因此,只要键类型成为并保持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 string 或undefined 中。 (所以K3 可以是"charAt",或任何string 方法和属性)。假设K3 是undefined。那么T3 就是string(因为它与T2 相同)。如果K4、K5 和K6 的所有其余部分都是undefined,那么T4、T5 和T6 就只是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',并且K3 到K6 都将是undefined。这是上面的例子,所以T6 将是string。因此ret 的类型为string。
如果你输入了一个错误的密钥,上面的函数签名也应该对你大喊大叫:
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'] 会发生什么,所以它的行为就好像K1 是undefined。如果你修复了第一个错误,第二个就会消失。可能有办法改变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;
这几乎是一样的,除了当键与它们应该匹配的不匹配时出现问题。