You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript映射类型中可选参数被误设为必填的解决方法

解决TypeScript事件参数可选属性无法省略的问题

问题核心在于ParamsSchemaToObject类型的定义:当前它仅做了参数类型的映射,但没有把Schema中标记为可选的参数(值为"number?"或"string?"的key)转换为对象的可选属性。即便参数类型包含undefined,TypeScript仍会要求该属性必须存在(值可以是undefined),而我们需要的是属性本身可以完全省略。

修改方案

我们可以拆分Schema中的必填与可选参数,分别构建类型后合并:

  1. 先定义两个辅助类型,分离必填和可选参数的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];
  1. 重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:41:04