下面是一些示例代码来说明这个问题:

enum ServicePlugin {
  Plugin1,
  Plugin2,
  Plugin3,
}

interface PluginOptions {
  [ServicePlugin.Plugin1]: { option1: string };
  [ServicePlugin.Plugin2]: { option1: number; option2: number };
}

type PluginOptionsMap = Omit<
  { [key in ServicePlugin]: undefined },
  keyof PluginOptions
> &
  PluginOptions;

type PluginConfig<T extends ServicePlugin> =
  PluginOptionsMap[T] extends undefined
    ? { plugin: T }
    : {
        plugin: T;
        options: PluginOptionsMap[T];
      };

const params: PluginConfig<ServicePlugin>[] = [
  {
    plugin: ServicePlugin.Plugin1,
    options: {
      option1: "foo",
    },
  },
  {
    plugin: ServicePlugin.Plugin2,
    options: {
      option1: 123,
      option2: 456,
    },
  },
  {
    plugin: ServicePlugin.Plugin2,
    options: {
      option1: "bar", // No error here even though it is the wrong options type
    },
  },
  {
    plugin: ServicePlugin.Plugin3,
    // Error: 'options' is not declared here
  },
];

类型PluginConfig<ServicePlugin>[]的行为更像是所有插件的联合数组,而不是单个插件的array.这是有道理的,但这不是我想要的.

How can I create an array of generic objects where the generic type of each object is a particular enum value?

推荐答案

您可能希望PluginConfig是特定的(非通用的)union类型,其中每个成员对应于ServicePlugin union enum的一个成员.就像这样:

type PluginConfig = {
    plugin: ServicePlugin.Plugin1;
    options: {
        option1: string;
    };
} | {
    plugin: ServicePlugin.Plugin2;
    options: {
        option1: number;
        option2: number;
    };
} | {
    plugin: ServicePlugin.Plugin3;
    options?: never;
}

请注意,在Plugin3的情况下,我得到了the impossible never typeoptionsoptional property.这实际上阻止了options属性的存在(或者至少不能是undefined以外的任何属性).可选表示可以省略它,而never表示不能使用它.


无论如何,我们可以通过distributive object type(如在microsoft/TypeScript#47109中创造的)使编译器根据ServicePluginPluginOptions的定义计算PluginConfig:

type PluginConfig = { [K in ServicePlugin]:
    K extends keyof PluginOptions ?
    { plugin: K, options: PluginOptions[K] } :
    { plugin: K, options?: never }
}[ServicePlugin];

一般形式{ [K in ServicePlugin]: F<K> }[ServicePlugin]mapped type除以ServicePlugin,然后立即index intoServicePlugin相加,有效地将F<K>除以ServicePlugin并集,所以它将变成F<ServicePlugin.Plugin1> | F<ServicePlugin.Plugin2> | F<ServicePlugin.Plugin3>.

我们使用的特定F<K>运算是K extends keyof PluginOptions ? { plugin: K, options: PluginOptions[K] } : { plugin: K, options?: never }.这是conditional type分.无论如何,plugin属性只是K,但是如果KPluginOptions的键,那么options属性就是相应值,而如果不是,那么我们通过使其成为可选的never属性来禁止options.

您可以验证它是否生成与上面的版本等价的类型.


让我们确保它以您想要的方式工作:

const params: PluginConfig[] = [
    {
        plugin: ServicePlugin.Plugin1,
        options: {
            option1: "foo",
        },
    },
    {
        plugin: ServicePlugin.Plugin2,
        options: {
            option1: 123,
            option2: 456,
        },
    },
    {
        plugin: ServicePlugin.Plugin2,
        options: {
            option1: "bar", // error,
        },
    }, // Types of property 'options' are incompatible.
    {
        plugin: ServicePlugin.Plugin3, // okay
    },
];

看上go 不错.第三个数组元素是错误的,因为您使用的是带有选项ServicePlugin.Plugin1ServicePlugin.Plugin2.最后一个数组元素是允许的,因为您省略了可选的options属性.

Playground link to code

Typescript相关问答推荐

相同端点具有不同响应时的RTKQuery类型定义

更新:Typescript实用程序函数合并对象键路径

带有联合参数的静态大小数组

webpack错误:模块找不到.当使用我的其他库时,

异步动态生成Playwright测试,显示未找到测试

如何使用类型谓词将类型限制为不可赋值的类型?

限制返回联合的TS函数的返回类型

Vue3 Uncaught SyntaxError:每当我重新加载页面时,浏览器控制台中会出现无效或意外的令牌

正确使用相交类型的打字集

如何正确调用创建的类型?

基于泛型的条件接口键

如何从上传文件中删除预览文件图标?

错误TS7030:并非所有代码路径都返回值.";由tsfig.json中的";Strong";:False引起

如何创建内部和外部组件都作为props 传入的包装器组件?

使用强类型元组实现[Symbol.iterator]的类型脚本?

有没有办法在Reaction组件中动态导入打字脚本`type`?

如何在Type脚本中创建一个只接受两个对象之间具有相同值类型的键的接口?

如何实现允许扩展泛型函数参数的类型

在类型{}上找不到带有string类型参数的索引签名 - TypeScript

如何让 Promise 将它调用的重载函数的类型转发给它自己的调用者?