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

如何在TypeScript中通过setter初始化readonly或私有属性

解决方案

你遇到的问题本质上有两个:

  1. TypeScript 无法推断Object.assign会完成_uri属性的初始化,所以会报初始化缺失的错误
  2. 你存在一个认知误区:Object.assign是直接对实例的属性进行赋值,不会触发类的 setter 方法,所以你原来的设计中 uri 的转换校验逻辑根本不会执行,就算解决了TS报错,运行时也会出现类型问题。

最优实现方案

这个方案可以完全保留你原来的设计思路,不需要把校验逻辑放到构造函数里,也能保留Object.assign的便捷性:

  1. 给_uri属性添加明确赋值断言(!),告诉 TypeScript 你保证该属性会在构造函数中完成初始化,跳过初始化检查
  2. 构造函数中单独处理uri属性,主动调用 setter 触发转换校验逻辑
  3. 剔除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 23:24:03