【问题标题】:Conditional tuple infered from rest parameters从剩余参数推断的条件元组
【发布时间】:2020-04-09 12:18:15
【问题描述】:

是否可以从键/值映射推断元组?

基本上它可能是从联合类型到元组的转换(比如https://github.com/microsoft/TypeScript/issues/13298#issuecomment-482330241

但我希望有一个更优雅和编译器友好的解决方案,比如手册中建议的元组函数 (http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#example-2)

interface ParamMapping {
    a: boolean;
    b: string;
    c: 'foo' | 'bar';
}

declare function param<K extends keyof ParamMapping>(...name: K[]): ParamMapping[K];
// posible solutions:
// declare function param<K extends keyof ParamMapping>(...name: K[]): ParamMapping[...K];
// declare function param<K extends keyof ParamMapping>(...name: K[]): ...ParamMapping[K];
// declare function param<K extends (keyof ParamMapping)[]>(...name: K): for P of K : ParamMapping[K];
declare function param(...name: string[]): unknown[];

// typed as unknown[]
const p1 = param('baz', 'qux');

// *should be* typed as [boolean, string]
const p2 = param('a', 'b');

// *should be* typed as [string, 'foo' | 'bar']
const p3 = param('b', 'c');

// *should be* typed as [boolean, string, 'foo' | 'bar']
const p4 = param('a', 'b', 'c');

Playground here

【问题讨论】:

    标签: typescript types tuples


    【解决方案1】:

    我认为您想使用mapped tuple types,将第一个重载签名更改为:

    declare function param<T extends Array<keyof ParamMapping>>(...name: T): {
        [I in keyof T]: ParamMapping[Extract<T[I], keyof ParamMapping>]
    };
    

    这里,nameT 类型,它本身就是一个数组或我们需要映射的键元组。如果我们将name 作为类型K[] 用于某些键类型K,那么编译器将不会跟踪顺序,因为K[] 不是元组。请注意,编译器不理解 I 将始终是类似数字的索引,因此我需要编写 Extract&lt;T[I], keyof ParamMapping&gt; 以获取可以索引到 ParamMapping 的内容。

    您可以验证它的行为是否符合您的要求:

    const p1 = param('baz', 'qux');
    // const p1: unknown[]
    
    const p2 = param('a', 'b');
    // const p2: [boolean, string]
    
    const p3 = param('b', 'c');
    // const p3: [string, "foo" | "bar"]
    
    const p4 = param('a', 'b', 'c');
    // const p4: [boolean, string, "foo" | "bar"]
    

    好的,希望对您有所帮助。祝你好运!

    Playground link

    【讨论】:

    • 您好,首先感谢您的回答。这解决了一半的问题,因为您解决了从 param 函数中删除自动完成功能。这就是我想要重载而不是 keyof ParamMapping | string 作为 param args 类型。有什么想法吗?
    • 嗯,但您的问题中没有提到自动完成功能,您的原始版本也没有自动完成功能,据我所知,这解决了问题的整个问题。当您有重载时,在您输入 param(" 时,您的 IDE 可能会选择 string[] 重载,因为字符串将是 "",这不是键之一......并且不会自动完成。如果我稍后有时间,我可能会回到这个并处理自动完成。祝你好运!
    • 请注意,使用也接受任何字符串的字符串文字进行自动完成的想法是open issue,目前还没有完美的解决方案。 "a" | "b" | string 只是 string,编译器会忘记 "a""b"。如果它接受string 但仍记得"a""b" 在完成列表中提供,那就太好了,但事实并非如此。这可能是一个单独的问题,可能没有一个好的答案。再次祝你好运!
    • 我认为是的,这是一个单独的问题,但“容易”回答。长话短说,如果文字扩展的原始类型也在联合中,编译器就会丢失联合类型的文字。 (more here)
    【解决方案2】:

    编辑 我的“解决方案”实际上在很多事情上都错了

    这是一个改进的工作示例。在某些部分有点hacky,但它可以完成工作:

    // Base interface that can be latter augmented
    interface ParamMapping {
        a: boolean;
        b: string; 
        c: 'foo' | 'bar';
    }
    
    // handle every other cases as unknown typed
    interface ExtendedParamMapping extends ParamMapping {
        [P: string]: unknown;
    }
    
    // hack to handle autocomplete on: "foo" | "bar" | string
    type UnknownMapping = string & { _?: never }
    
    // Yes. Named like this.
    type Jcalz<T> = {
        [I in keyof T]: ExtendedParamMapping[Extract<T[I], keyof ParamMapping>]
    };
    
    declare function param<T extends Array<keyof ParamMapping>, U extends T | UnknownMapping[]>(...name: U | T): Jcalz<U>;
    
    const p1 = param('baz', 'qux');
    // const p1: [unknown, unknown]
    
    const p2 = param('a', 'b');
    // const p2: [boolean, string]
    
    const p3 = param('b', 'c');
    // const p3: [string, "foo" | "bar"]
    
    const p4 = param('a', 'b', 'c');
    // const p4: [boolean, string, "foo" | "bar"]
    
    const p5 = param('a', 'baz');
    // const p6: [boolean, unknown]
    
    const p6 = param('foo', 'c');
    // const p4: [unknown, "foo" | "bar"]
    

    Playground link


    原答案

    (作为帖子回答,不要在评论部分充斥代码,因为它解决了我的问题)

    我想我已经找到了如何使用 jcalz answer 处理自动完成功能:

    interface ParamMapping {
        a: boolean;
        b: string;
        c: 'foo' | 'bar';
    }
    
    type UnknownMapping = string & { _?: never }
    
    // Yes. Named like this.
    type Jcalz<T> = {
        [I in keyof T]: ParamMapping[Extract<T[I], keyof ParamMapping>]
    };
    
    type MappedType<T> = T extends keyof ParamMapping ? Jcalz<T> : unknown;
    
    declare function param<T extends Array<keyof ParamMapping>>(...name: T | UnknownMapping[]): MappedType<T>;
    // declare function param(...name: string[]): unknown[]; // not needed anymore
    
    const p1 = param('baz', 'qux');
    // const p1: unknown[]
    // "basic" string works!
    
    const p2 = param('a', 'b');
    // const p2: [boolean, string]
    // autocomplete works!
    

    不需要其他重载,原始界面保持不变,并且自动完成工作。 (灵感来自这里https://github.com/microsoft/TypeScript/issues/29729#issuecomment-460346421

    Playground link

    我会将jcalz answer 固定为已接受。

    【讨论】:

      猜你喜欢
      • 2019-02-22
      • 1970-01-01
      • 2020-01-25
      • 1970-01-01
      • 1970-01-01
      • 2021-03-01
      • 2021-11-15
      • 2022-01-03
      • 1970-01-01
      相关资源
      最近更新 更多