如何在TypeScript中通过setter初始化readonly或私有属性
解决方案
你遇到的问题本质上有两个:
- TypeScript 无法推断
Object.assign会完成_uri属性的初始化,所以会报初始化缺失的错误 - 你存在一个认知误区:
Object.assign是直接对实例的属性进行赋值,不会触发类的 setter 方法,所以你原来的设计中 uri 的转换校验逻辑根本不会执行,就算解决了TS报错,运行时也会出现类型问题。
最优实现方案
这个方案可以完全保留你原来的设计思路,不需要把校验逻辑放到构造函数里,也能保留Object.assign的便捷性:
- 给
_uri属性添加明确赋值断言(!),告诉 TypeScript 你保证该属性会在构造函数中完成初始化,跳过初始化检查 - 构造函数中单独处理
uri属性,主动调用 setter 触发转换校验逻辑 - 剔除
config中的uri字段后,再用Object.assign覆盖剩余配置项
修改后的完整代码如下:
import ConfigurationInterface from '../contract/BaseUriConfigurationInterface' import Uri from '../Uri' import UriSyntaxError from '../error/UriSyntaxError' import ConfigurationError from '../error/ConfigurationError' export default class BaseUriConfiguration implements ConfigurationInterface { // 加感叹号做明确赋值断言 private _uri!: Uri readonly mergePath: boolean readonly mergeQuery: boolean readonly defaultScheme: 'http' | 'https' | null readonly overrideScheme: boolean constructor(config : ConfigurationInterface) { this.mergePath = true this.mergeQuery = false this.defaultScheme = null this.overrideScheme = false // 单独处理uri,主动触发setter走转换校验逻辑 this.uri = config.uri // 剔除uri后赋值剩余配置项 const { uri, ...restConfig } = config Object.assign(this, restConfig) } public get uri() : Uri { return this._uri } private set uri(uri: Uri | string) { if(typeof uri === 'string') { try { uri = new Uri(uri) } catch(error) { if(! (error instanceof UriSyntaxError)) { throw error } const msg = `Invalid base uri given. See previous error for more details.` throw new ConfigurationError(msg, error) } } this._uri = uri } }
多特殊属性的扩展方案
如果你后续有多个需要做转换/校验的配置项,可以写通用的映射逻辑,不需要每个属性都单独写一行赋值,所有处理逻辑仍然保留在各自的setter中:
constructor(config : ConfigurationInterface) { // 先批量赋值默认值 Object.assign(this, { mergePath: true, mergeQuery: false, defaultScheme: null, overrideScheme: false }) // 注册需要走setter的属性映射 const setterMap: Record<string, (val: any) => void> = { uri: val => this.uri = val, // 后续新增的带校验的属性在这里加映射即可 } // 遍历配置项赋值 Object.entries(config).forEach(([key, value]) => { if (setterMap[key]) { setterMap[key](value) return } // @ts-ignore 剩余普通属性直接赋值即可 this[key] = value }) }
方案优势
- 完全保留了setter的校验转换逻辑,不需要把代码塞到构造函数中,避免构造函数臃肿
- 不需要手动给每个普通配置项赋值,仍然保留了批量赋值的便捷性
- 类型安全,所有校验逻辑都能正常触发,不会出现运行时类型问题
- 扩展性好,后续新增配置项只需要加对应的setter和映射即可,不需要修改核心赋值逻辑
内容的提问来源于stack exchange,提问作者Nonetallt
相关产品推荐
相关产品推荐

