您可以使用递归条件类型来遍历路径元组并检索目标 - 递归直到我们没有路径。
我不太确定 value() 在您的示例中的签名是什么 - 我相信这可能只是 Promise,但也许我遗漏了一些东西。
编辑 - 更新以添加自动完成功能。根据@jcalz 对这些问题的回答,我对其进行了调整,为参数添加了 cmets 和名称。
TypeScript type definition for an object property path
这里的要点是我们需要建立一个包含有效属性访问器序列的元组联合 - 即 for
{ a: { b: { c: 'foo' } } }
你有以下元组:
['a']
['a', 'b']
['a', 'b', 'c']
要注意的另一件事是,任何递归类型都需要某种退出条件,这样编译器就知道你不会无限遍历。鉴于我们正在挖掘一个对象——而不是迭代一个数组——我们可以使用一个计数器并在每次递归时递减它。当计数器达到最终值never - 我们放弃递归。
此处默认限制(如果未提供)设置为 10。(RecursiveDepthCounter extends number = 10)。
type PreviousNumber = [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<Target extends object, RecursiveDepthCounter extends number = 10> =
// Check if we've hit recursive depth, if so, bail
[RecursiveDepthCounter] extends [never]
? never
: {
[key in keyof Target]: Target[key] extends infer TargetChild
? TargetChild extends object
// If we have an object at TargetChild, then we can either access this object ([key])
// or we need to recurse by calling Paths again, decrementing our recursive depth counter and appending
// to the tuple of acceptable keys
? [key] | [key, ...Paths<TargetChild, PreviousNumber[RecursiveDepthCounter]>]
// If we don't have an object, only key is permissable
: [key]
// If we can't infer Target[key], only allow [key]
: [key]
// Access resulting object via keyof Target to remove nesting
}[keyof Target];
type PropertyAtPath<Target extends unknown, Path extends readonly unknown[]> =
// Base recursive case, no more paths to traverse
Path extends []
// Return target
? Target
// Here we have 1 or more paths to access
: Path extends [infer TargetPath, ...infer RemainingPaths]
// Check Target can be accessed via this path
? TargetPath extends keyof Target
// Recurse and grab paths
? PropertyAtPath<Target[TargetPath], RemainingPaths>
// Target path is not keyof Target
: never
// Paths could not be destructured
: never;
export type IReference<T extends object> = {
property<P extends Paths<T>>(...paths: [...P]): IReference<PropertyAtPath<T, P>>;
value(): Promise<T>;
}
const data = { foo: { bar: { value: 'foobar' } } };
declare const ref0: IReference<typeof data>;
const ref1 = ref0.property('foo', 'bar');
const value = await ref1.value() // is string
TS 游乐场:hhttps://tsplay.dev/WGkyoW