TypeScript额外属性检查异常:编译器允许非法额外属性
TypeScript允许Command类型添加额外属性的原因及解决办法
问题根源
你碰到的这个问题,核心原因有两点:
- Command的默认泛型参数太宽松:Command的Data参数默认是
CommandData,而CommandData带[key: string]: unknown的索引签名——这种类型会让TypeScript认为,只要对象有字符串键的属性,不管是什么,都符合类型要求。 - 结构类型系统的特性:TypeScript是结构类型,只要对象包含目标类型的所有必填属性,额外属性不会被拦截;再加上你这里是用对象展开后返回,不会触发字面量的额外属性检查(这个检查只在直接把字面量赋值给类型变量时生效)。
解决办法
根据你的需求,有几种收紧类型约束的方式:
1. 收紧Command的默认Data类型
把Command的Data默认参数从CommandData改成空对象{},这样默认情况下Command不允许任何额外属性:
type CommandData = { [key: string]: unknown } type BaseState = { id: string; } export type Command< Type extends string = string, Data extends CommandData = {}, // 修改默认值为空对象 > = { id: string; payload: Data; type: Type; }; export type PreHook<StateModel extends BaseState = BaseState, Commands extends Command = Command> = ({ command, state, }: { command: Commands; state: StateModel; }) => Promise<{ command: Commands; state: StateModel }>; // 现在这里会报错,metadata和foo不属于Command的属性 const preHook: PreHook = async ({ command, state }) => ({ command: { ...command, metadata: { foo: "bar" }, foo: "bar" }, state: { ...state }, });
2. 显式指定PreHook的泛型参数
如果需要保留Command的宽松默认,但在特定PreHook中用严格类型,可以手动指定泛型:
// 明确指定Commands为Data是空对象的Command类型 const preHook: PreHook<BaseState, Command<string, {}>> = async ({ command, state }) => ({ command: { ...command, metadata: { foo: "bar" }, foo: "bar" }, // 此处会触发类型错误 state: { ...state }, });
3. 用精确类型彻底禁止额外属性
如果需要彻底杜绝任何额外属性(哪怕是带索引签名的情况),可以借助精确类型工具:
// 定义精确类型工具:确保T和U的结构完全一致 type Exact<T, U> = T extends U ? (U extends T ? T : never) : never; // 修改PreHook的返回类型,要求command必须是精确的Commands类型 export type PreHook<StateModel extends BaseState = BaseState, Commands extends Command = Command> = ({ command, state, }: { command: Commands; state: StateModel; }) => Promise<{ command: Exact<Commands, typeof command>; state: StateModel }>; // 此时返回带额外属性的command会直接报错 const preHook: PreHook = async ({ command, state }) => ({ command: { ...command, metadata: { foo: "bar" }, foo: "bar" }, state: { ...state }, });
内容的提问来源于stack exchange,提问作者florian norbert bepunkt
相关产品推荐
相关产品推荐

