【问题标题】:Method type that takes path segments as arguments and returns the type at the end of the path将路径段作为参数并返回路径末尾的类型的方法类型
【发布时间】:2021-11-02 00:37:28
【问题描述】:

我正在为网络工作者编写一个小的 RPC 库,它需要消费者遍历远程引用。要访问参考,您必须使用 IReference.property(...path: string[])

例如,如果我有一个看起来像 { foo: { bar: { value: 'foobar' }}} 的源对象

然后我将使用 await ref.property('foo', 'bar', 'value').value() 访问内部值

我希望.value() 的返回值成为该值的Promise

我已经设法编写了一种类型,允许我在 property 方法中拥有一个路径段,但我该如何添加更多?

export interface IReference<T> {
  property<K extends keyof T | ((...args: any) => any)>(key: K): K extends keyof T ? IReference<T[K]> : any;
  value(): T extends (...args: any) => any ? any : Promise<T>;
}

const data = { foo: { bar: { value: 'foobar' }}}
declare const ref0: IReference<typeof data>

const ref1 = ref0.property('foo', 'bar', 'value')
const value = await ref1.value() // should be string

TypeScript Playground

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以使用递归条件类型来遍历路径元组并检索目标 - 递归直到我们没有路径。

    我不太确定 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

    【讨论】:

    • 这是我见过的最疯狂的类型签名。这种类型签名是否有性能考虑?
    • 有一点是不必要的,所以我更新了一个简化版本。所以 - TS 已经随着时间的推移进行了更新,以引入围绕这种事情的约束 - 递归深度限制,惰性评估。我不认为这会造成性能问题——如果是这样,那将是编译时间或编辑器响应能力,但这对 TS 来说并不算太疯狂。这对 TS 来说真的很疯狂,似乎表现不错:tsplay.dev/lWY33N
    • 一个问题是我在方法参数中键入“,”时没有路径智能感知。如果路径无效,我想得到一个类型错误
    • 嗯 - 这是一个很好的观点。应该可以,如果我能找到方法会告诉你
    • 更新添加智能感知 ^ @DavidAlsh
    猜你喜欢
    • 2012-06-22
    • 2023-03-05
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 2014-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多