TypeScript中如何让Logger构造函数默认属性在log方法中可选?
实现动态必填项的Logger类
我有一个Logger类,构造函数可以接收可选的默认配置:
interface Log { level: string scope: string source: string message: string } type LogDefaults = Partial<Log> type LogInput = Log class Logger { constructor(private defaults: LogDefaults = {}) {} public log(log: LogInput): void { console.log({ ...this.defaults, ...log }) } }
我希望构造函数传入的defaults属性,在log方法中自动变为可选属性。能不能定义LogInput类型,让只有未在LogDefaults中提供的属性才是必填项?
要求以下场景正常工作:
const logger = new Logger() logger.log({ scope: 'my-scope', source: 'my-souce', level: 'info', message: 'my message' })
以下场景也正常工作:
const logger = new Logger({ scope: 'my-scope', source: 'my-souce' }) logger.log({ level: 'info', message: 'my message' })
以下场景应该报错(缺少必填的source属性):
const logger = new Logger({ scope: 'my-scope' }) logger.log({ level: 'info', message: 'my message' })
解决方案:用泛型约束实现动态必填项
要实现这个需求,需要把Logger改为泛型类,让构造函数的defaults类型作为泛型参数,以此精准推导log方法的入参类型。具体实现如下:
interface Log { level: string scope: string source: string message: string } // 泛型类,D捕获构造时传入的defaults的具体类型 class Logger<D extends Partial<Log>> { constructor(private defaults: D) {} // 拼接出动态入参类型:未默认的属性必填,已默认的属性可选 public log(log: Omit<Log, keyof D> & Partial<Pick<Log, keyof D>>): void { console.log({ ...this.defaults, ...log }) } }
代码说明
- 泛型参数D:替代原本宽泛的
Partial<Log>,精准记录用户传入的默认属性,让TypeScript知道哪些属性已有默认值。 - 动态入参类型:
Omit<Log, keyof D>:从Log中排除已设置默认值的属性,这些就是必填项(因为没有默认值兜底)。Partial<Pick<Log, keyof D>>:把已设置默认值的属性转为可选,允许用户在log时覆盖默认值。- 两者交叉后,最终入参就会严格遵循「缺啥必填啥」的规则。
场景验证
- 无默认值的情况:
const logger = new Logger({}) logger.log({ scope: 'my-scope', source: 'my-souce', level: 'info', message: 'my message' }) // 正常:所有属性都无默认值,必须完整传入
- 带部分默认值的情况:
const logger = new Logger({ scope: 'my-scope', source: 'my-souce' }) logger.log({ level: 'info', message: 'my message' }) // 正常:scope和source已有默认值,只需传入剩余必填的level和message
- 缺少必填项的情况:
const logger = new Logger({ scope: 'my-scope' }) logger.log({ level: 'info', message: 'my message' }) // 报错:source未设置默认值,属于必填项,此处未传入
额外优化:支持无参数初始化
如果想要保留new Logger()这种无参数写法,可以给构造函数的defaults加默认值,并设置泛型参数的默认值:
class Logger<D extends Partial<Log> = {}> { constructor(private defaults: D = {} as D) {} public log(log: Omit<Log, keyof D> & Partial<Pick<Log, keyof D>>): void { console.log({ ...this.defaults, ...log }) } } // 现在可以直接写: const logger = new Logger() logger.log({ /* 所有属性必填 */ })
内容的提问来源于stack exchange,提问作者Renato
相关产品推荐
相关产品推荐

