如何根据一个属性是否在另一个类型中存在来有条件地选择类型?

时间:2018-06-21 10:27:16

标签: typescript conditional-types

首先,我想要以下结果:

type Wrapper<ID extends string> = { id: ID };
type WrapperWithPayload<ID extends string, Payload> = { id: ID, payload: Payload };

enum IDs {
  FOO = "ID Foo",
  BAR = "ID Bar",
  BAZ = "ID Baz"
}

interface AssociatedTypes {
  [IDs.FOO]: number;
  [IDs.BAR]: number[];
}

type Result = MagicMapper<IDs, AssociatedTypes>
/*
 * Result should have this type:
 * {
 *   [IDs.FOO]: WrapperWithPayload<IDs.FOO, number>;
 *   [IDs.BAR]: WrapperWithPayload<IDs.BAR, number[]>;
 *   [IDs.BAZ]: Wrapper<IDs.BAZ>;
 * }
 */

换句话说,我想提供一些字符串和一个映射,这些映射将那些字符串的子集映射到另一种类型。然后,我想使用字符串作为键来创建一个新类型。对于每个字符串/键,如果存在映射,则我要使用类型A,否则要使用类型B。

现在,我的方法如下:

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping[key] extends never ? Wrapper<key> : WrapperWithPayload<key, Mapping[key]>;
};

那几乎可以达成目标:

type Result = {
  "ID Foo": WrapperWithPayload<IDs.FOO, number>;
  "ID Bar": WrapperWithPayload<IDs.BAR, number[]>;
  "ID Baz": Wrapper<IDs.BAZ> | WrapperWithPayload<IDs.BAZ, any>;
}

Baz键上的联合是错误的。我认为该错误位于extends never条件内,但是用例如undefined代替它只会使情况更糟:

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping[key] extends undefined ? Wrapper<key> : WrapperWithPayload<key, Mapping[key]>;
};

type Result = {
  "ID Foo": Wrapper<IDs.FOO>;
  "ID Bar": Wrapper<IDs.BAR>;
  "ID Baz": Wrapper<IDs.BAZ>;
}

有什么方法可以使事情按我需要的方式工作?

1 个答案:

答案 0 :(得分:1)

您可以将Mapping中密钥存​​在的测试更改为Mapping extends { [P in key]: infer U },一切都会按预期进行:

type Wrapper<ID extends string> = { id: ID };
type WrapperWithPayload<ID extends string, Payload> = { id: ID, payload: Payload };

enum IDs {
    FOO = "ID Foo",
    BAR = "ID Bar",
    BAZ = "ID Baz"
}

interface AssociatedTypes {
    [IDs.FOO]: number;
    [IDs.BAR]: number[];
}

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping extends { [P in key]: infer U } ? WrapperWithPayload<key, U>: Wrapper<key> ;
};
type Result = MagicMapper<IDs, AssociatedTypes> 
// same as :
type Result = {
    "ID Foo": WrapperWithPayload<IDs.FOO, number>;
    "ID Bar": WrapperWithPayload<IDs.BAR, number[]>;
    "ID Baz": Wrapper<IDs.BAZ>;
}

Playground link