TypeScript中如何将通过MyClass.defaults设置的必填构造参数设为可选
实现方案
interface BaseOptions { version: string; [key: string]: unknown; } // 工具类型:无剩余必填属性时构造函数参数自动变为可选 type OptionalIfEmpty<T> = {} extends T ? (options?: Partial<T>) => void : (options: Partial<T>) => void; type BaseConstructor<TOptions extends BaseOptions = BaseOptions> = & (new (...args: ConstructorParameters<OptionalIfEmpty<Omit<TOptions, keyof BaseOptions>>> & [options?: Partial<TOptions>]) => Base<TOptions>) & { defaults<TDefaults extends Partial<TOptions>>( defaults: TDefaults ): BaseConstructor<Omit<TOptions, keyof TDefaults> & TDefaults>; }; class Base<TOptions extends BaseOptions = BaseOptions> { static defaults<TDefaults extends Partial<BaseOptions>>( this: BaseConstructor, defaults: TDefaults ): BaseConstructor<Omit<BaseOptions, keyof TDefaults> & TDefaults> { return class extends (this as any) { constructor(...args: any[]) { super(Object.assign({}, defaults, args[0] || {})); } } as any; } constructor(options: TOptions) { this.options = options; } options: TOptions; }
效果验证
所有需求场景均可正常通过类型校验:
// 原始Base构造必须传version const test = new Base({ version: "1.2.3" }) // defaults方法入参version可选 const MyBaseWithDefaults = Base.defaults({ foo: "bar" }) const test2 = new MyBaseWithDefaults({ version: "2.0.0" }) // defaults传version后新构造无需传version const MyBaseWithVersion = Base.defaults({ version: "1.2.3", foo: "bar" }) const testWithDefaults = new MyBaseWithVersion() // 以下两个属性均被自动推导为string类型 testWithDefaults.options.version testWithDefaults.options.foo // 支持级联defaults调用 const CascadedBase = Base.defaults({ one: "1" }).defaults({ two: "2" }).defaults({ version: "3.0.0" }) const cascadedTest = new CascadedBase() cascadedTest.options.one // string cascadedTest.options.two // string cascadedTest.options.version // string
核心逻辑说明
- 用
BaseConstructor类型统一封装构造函数签名和defaults方法签名,保证级联调用时泛型类型正确传递 - 通过工具类型
OptionalIfEmpty判断当前剩余必填属性是否为空,为空时构造函数参数自动变为可选 - 每次调用
defaults都会合并传入的默认值到泛型参数中,自动过滤已经设置默认值的必填属性
内容的提问来源于stack exchange,提问作者Gregor
相关产品推荐
相关产品推荐

