【问题标题】:Typescript Typings: array of T to mapTypescript Typings:要映射的 T 数组
【发布时间】:2019-07-31 21:07:46
【问题描述】:

假设我们有一个类型T

type T = {
  type: string,
}

还有一个函数,它接受 T 的数组并返回一个对象,其键是每个 T.type 的值,其值为 T

const toMap = (...args: T[]) => args.reduce((res, t) => ({
  ...res,
  [t.type]: t
}), {});

所以,对于这个给定的例子:

const a = { type: 'hello' };
const b = { type: 'world' };
const c = { type: 'foo' };

const map = toMap(a, b, c);

我期待这个结果

{
  hello: { type: 'hello' },
  world: { type: 'world' },
  foo: { type: 'foo' },
}

map.hello // correct, { type: 'hello' };

// If I access an unknown property, then the compiler should: 
map.bar // `property bar doesn't exist on type { hello: { ... }, world: {...}, foo: {...} }`

如何为这个函数编写类型?

【问题讨论】:

    标签: javascript typescript generics typescript-typings typescript-generics


    【解决方案1】:

    您可以先让T 真正通用:

    function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
      return args.reduce((res, t) => ({
        ...res,
       [t.type]: t
      }), {});
    }
    

    为了能够真正缩小类型,您必须为可变参数键入泛型类型,例如toMap&lt;A&gt;(arg1: A), toMap&lt;A, B&gt;(arg1: A, arg2: B).

    但有两个缺点:

    1) 您必须为任意数量的参数创建这些重载, 但是这在 Typescript 中很常见(请参阅Object.assign 声明)。

    2) 默认情况下,Typescript 类型 { type: "test" }{ type: string }(99% 的情况下都需要),但是我们不能直接将键类型推断为 "test"。为了解决这个问题,我们必须将字符串文字类型转换为缩小的字符串类型{ type: "test" as "test" }

    // generic overload for one argument
    function toMap<A>(arg: A): { [K1 in O<A>]: A };
    
    // generic overload for two arguments:
    function toMap<A, B>(arg: A, arg2: B): { [K in O<A>]: A } & { [K in O<B>]: B };
    
    // generic overload for three arguments:
    function toMap<A, B, C>(arg: A, arg2: B, arg3: C): { [K in O<A>]: A } & { [K in O<B>]: B } & { [K in O<C>]: C };
    
    // ... repeat for more arguments
    
    // implementation for all kind of args
    function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
       return args.reduce((res, t) => ({
         ...res,
        [t.type]: t
      }), {});
    }
    
    // Infers the type of "type", which has to be a string, from a given object
    type O<V> = V extends { type: infer K } ? K extends string ? K : never : never;
    
    // Narrow down a.type to be "test" instead of string
    const a = { type: "test" as "test" }
    const b = { type: "test2" as "test2", v: 1 };
    
    const test = toMap(a);
    const test2 = toMap(a, b);
    
    console.log(
     test2.test2.v, // works!
     test2.whatever, // doesnt!
     test2.test2.k // doesnt!
    );
    

    Try it!

    【讨论】:

    • 我觉得这行不通,keys应该是T.type的值
    • 我认为要走的路是keyOf
    • 这很好,但在我的真实案例中,我无法执行类型断言as &lt;literal&gt;,并且它不起作用。 (由于初始重载,如果您传递超过 1 个参数,它也会出错)
    • @htimands 如果你不能做类型断言,你的代码在编译时不是静态的,因此你不能缩小类型
    • 这是我正在处理的包github.com/Code-Y/redux-fluent
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-16
    • 2021-07-24
    • 1970-01-01
    • 2020-05-25
    • 2018-06-18
    • 2018-02-05
    相关资源
    最近更新 更多