TypeScript映射类型中可选参数被误设为必填的解决方法
解决TypeScript事件参数可选属性无法省略的问题
问题核心在于ParamsSchemaToObject类型的定义:当前它仅做了参数类型的映射,但没有把Schema中标记为可选的参数(值为"number?"或"string?"的key)转换为对象的可选属性。即便参数类型包含undefined,TypeScript仍会要求该属性必须存在(值可以是undefined),而我们需要的是属性本身可以完全省略。
修改方案
我们可以拆分Schema中的必填与可选参数,分别构建类型后合并:
- 先定义两个辅助类型,分离必填和可选参数的key:
// 提取Schema中必填参数的key(值不以?结尾) type RequiredParamsKeys<T extends ParamsSchema> = { [K in keyof T]: T[K] extends `${infer _}?` ? never : K; }[keyof T]; // 提取Schema中可选参数的key(值以?结尾) type OptionalParamsKeys<T extends ParamsSchema> = { [K in keyof T]: T[K] extends `${infer _}?` ? K : never; }[keyof T];
- 重构
ParamsSchemaToObject类型,将必填参数设为必填属性,可选参数设为可选属性:
type ParamsSchemaToObject<T extends ParamsSchema> = // 必填部分:属性必须存在 { [K in RequiredParamsKeys<T>]: ParamsTypeMap[T[K]]["type"] } & // 可选部分:属性可省略 { [K in OptionalParamsKeys<T>]?: ParamsTypeMap[T[K]]["type"] };
修改后的完整代码
type ParameType<T> = { regexSnippet: string parse?: (value: string) => any type: T } const ParameterTypes = { "string": <ParameType<string>>{ regexSnippet: "\\w+" }, "number": <ParameType<number>>{ regexSnippet: "\\d+", parse: parseInt }, "string?": <ParameType<undefined | string>>{ regexSnippet: "\\w*" }, "number?": <ParameType<undefined | number>>{ regexSnippet: "\\d*", parse: parseInt }, } as const type ParamsTypeMap = typeof ParameterTypes type ParamsTypeKeys = keyof ParamsTypeMap type ParamsSchema = { [name: string]: ParamsTypeKeys } // 新增辅助类型 type RequiredParamsKeys<T extends ParamsSchema> = { [K in keyof T]: T[K] extends `${infer _}?` ? never : K; }[keyof T]; type OptionalParamsKeys<T extends ParamsSchema> = { [K in keyof T]: T[K] extends `${infer _}?` ? K : never; }[keyof T]; // 重构后的类型 type ParamsSchemaToObject<T extends ParamsSchema> = { [K in RequiredParamsKeys<T>]: ParamsTypeMap[T[K]]["type"] } & { [K in OptionalParamsKeys<T>]?: ParamsTypeMap[T[K]]["type"] }; type Settings = { parameters: ParamsSchema } class Test<T extends { [name: string]: Settings } = {}> { protected events: T = {} as T registerEvent<K extends string, V extends Settings>( eventname: K, settings: V ): asserts this is Test<T & { [keyname in K]: V }> { // ... } emit<K extends keyof T>( eventname: K, parameters: ParamsSchemaToObject<T[K]["parameters"]> ): any { // ... } } const test: Test = new Test() test.registerEvent("prmtst", { parameters: { a: "number", b: "number?", c: "string" } }) test.emit("prmtst", { a: 1, b: 2, c: "str" }) // OK test.emit("prmtst", { a: 1, c: "str" }) // 现在正常,无错误 test.emit("prmtst", { a: 1, b: undefined, c: "str" }) // 也支持传入undefined
原理说明
RequiredParamsKeys通过条件类型筛选出Schema中值不以?结尾的key,对应必填参数;OptionalParamsKeys筛选出值以?结尾的key,对应可选参数;- 最终的
ParamsSchemaToObject合并两个类型:必填部分属性强制存在,可选部分属性带?修饰符,允许完全省略。
内容的提问来源于stack exchange,提问作者Daniel de Andrade Varela
相关产品推荐
相关产品推荐

