首页 > 解决方案 > 如何根据属性是否存在于另一种类型中有条件地选择类型?

问题描述

首先,我想要以下结果:

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>;
}

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

标签: typescriptconditional-types

解决方案


您可以更改测试是否存在密钥,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>;
}

游乐场链接


推荐阅读