下面是我的代码:

export type CommandOptionType = string | number | boolean;
export type CommandOption = Record<string, CommandOptionType>;

export type LeafCommand<T extends CommandOption> = {
    name: string;
    handle: (
        interactionOptionValues: { [K in keyof T]: T[K] },
    ) => Promise<void>;
};

export type LeafCommandGroup = {
    name: string;
    commands: Record<string, LeafCommand<CommandOption>>;
};

export type RootCommand = {
    name: string;
    commands: Record<string, LeafCommandGroup | LeafCommand<CommandOption>>;
};

这些类型的使用:

type OPTIONS = {
    option1: "type1" | "type2" | "type3" | "type4";
    option2: string;
    option3: boolean;
};

const Type = {
    name: "type",
    handle: async (interactionOptionValues) => {
        // business logic here
    },
} satisfies LeafCommand<OPTIONS>;

错误发生在代码段下面:

const Root = {
    name: "root",
    commands: { // error occurs here
        [Type.name]: Type,
    },
} satisfies RootCommand;

这里,我得到的错误是Type { [x: string]: CommandOptionType } is missing the following properties from type. option1, option2, option3.我不知道为什么Typescribe抱怨其他索引键也是字符串.我找不到工作.

期望的结果是,我应该能够添加命令,其中有LeafCommand类型没有一个拥挤.

如果你有任何其他更聪明的设计模式,我会为它.

谢谢你抽出时间

推荐答案

你的LeafCommand<T>类型是contravariant in T(参见Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript),因为T作为函数的参数类型出现. 这意味着LeafCommand<T> extends LeafCommand<U>如果U extends T,反之亦然. 所以从OPTIONS extends CommandOption开始,你只能得出LeafCommand<CommandOption> extends LeafCommand<OPTIONS>的结论.相反的,你所做的,是不正确的...因为那里不安全 例如:

let lcO: LeafCommand<OPTIONS> =
  { name: "", handle(o) { o.option2.toUpperCase(); return Promise.resolve() } };
let lcCO: LeafCommand<CommandOption> = lcO; // error, for good reason!
lcCO.handle({}); // error at runtime, o.option2 is undefined

如果你把LeafCommand<OPTIONS>赋给LeafCommand<CommandOption>,那么当你调用handle()方法并给它一个CommandOption时,实现会假设它得到了一个OPTIONS,如果这个假设是错误的,你很容易得到运行时错误.


如果你正在寻找一个类型,所有LeafCommand<T>都可以分配,无论T是什么,那么你需要一个类型,它是所有T都可以分配.唯一可行的方法是使用the never type:

type RootCommand = {
  name: string;
  commands: Record<string, LeafCommandGroup | LeafCommand<never>>;
};

const Root = {
  name: "root",
  commands: { // okay now
    [Type.name]: Type,
  },
} satisfies RootCommand;

这可能会也可能不会最终满足你的底层用例(如果你试图在LeafCommand<never>上使用call a handle(),你会发现自己不高兴,但这只是一个逆变的结果),但它至少是类型安全的.

Playground link to code

Typescript相关问答推荐

在Switch陈述中输入保护类

有没有办法适当缩小类型?

如何正确修复TypScript 4.7到4.8升级中的TS 2345编译错误

带有微前端的动态React路由问题

泛型函数类型验证

TypeScrip:基于参数的条件返回类型

如何在单击停止录制按钮后停用摄像机?(使用React.js和Reaction-Media-Recorder)

类型,其中一条记录为字符串,其他记录为指定的

如何将泛型对象作为返回类型添加到类型脚本中

从TypeScrip中的其他类型检索泛型类型

使用动态输出类型和泛型可重用接口提取对象属性

如何将父属性类型判断传播到子属性

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

在Cypress中,您能找到表格中具有特定文本的第一行吗?

如何从一个泛型类型推断多个类型

NPM使用Vite Reaction应用程序运行预览有效,但我无法将其部署到Netlify

react 路由不响应参数

使用Cypress测试从Reaction受控列表中删除项目

我可以使用对象属性的名称作为对象中的字符串值吗?

Select 器数组到映射器的Typescript 泛型