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

如何避免重复定义类的类型与构造函数传入属性?

将JavaScript Token类迁移至TypeScript时的DRY实现方案

问题描述

原JavaScript类通过Object.assign直接接收传入对象初始化实例,依赖调用者保证属性正确性:

export class Token {
  constructor(obj) {
    Object.assign(this, obj)
  }

  // 其他方法
}

迁移到严格模式TypeScript后,最初的实现存在TokenProps接口与类属性的重复定义,违背DRY原则:

interface TokenProps {
  index: number
  length: number
  op: string
  phrase: string
  quotes: 0 | 1 | 2
  wild: '' | '*'
}

export class Token {
  index: number
  length: number
  op: string
  phrase: string
  quotes: 0 | 1 | 2
  wild: '' | '*'

  constructor(obj: TokenProps) {
    this.index = obj.index
    this.length = obj.length
    this.op = obj.op
    this.phrase = obj.phrase
    this.quotes = obj.quotes
    this.wild = obj.wild
  }

  // 其他方法
}

当前尝试的方案

目前的写法避免了重复,但使用非空断言(!),担心不够严谨:

class TokenBase {
  index!: number
  length!: number
  op!: string
  phrase!: string
  quotes!: 0 | 1 | 2
  wild!: '' | '*'
}

type TokenProps = {[k in keyof TokenBase]: TokenBase[k]}

export class Token extends TokenBase {
  constructor(obj: TokenProps) {
    super()
    Object.assign(this, obj)
  }
}

优化方案与解释

方案1:简化当前实现,保留非空断言

你的当前方案其实是合理的,非空断言在这里是安全的:

  • TokenBase的属性用!标记,是因为TypeScript无法感知子类构造函数会通过Object.assign完成初始化
  • 实际运行时,Token的构造函数会立即给所有属性赋值,不存在未初始化的情况

可以进一步简化TokenProps的定义,直接复用TokenBase的实例类型:

class TokenBase {
  index!: number
  length!: number
  op!: string
  phrase!: string
  quotes!: 0 | 1 | 2
  wild!: '' | '*'
}

type TokenProps = TokenBase

export class Token extends TokenBase {
  constructor(obj: TokenProps) {
    super()
    Object.assign(this, obj)
  }

  // 其他方法
}

方案2:利用接口+Object.assign减少重复

如果不想使用基类,也可以通过接口定义属性,类实现接口后用Object.assign简化构造函数:

interface TokenProps {
  index: number
  length: number
  op: string
  phrase: string
  quotes: 0 | 1 | 2
  wild: '' | '*'
}

export class Token implements TokenProps {
  index: number
  length: number
  op: string
  phrase: string
  quotes: 0 | 1 | 2
  wild: '' | '*'

  constructor(obj: TokenProps) {
    Object.assign(this, obj)
  }

  // 其他方法
}

虽然类仍需声明属性,但TypeScript会强制属性与接口保持一致,且构造函数无需逐个赋值。

方案3:参数属性+解构赋值(适合属性较少的场景)

如果属性数量不多,可以用参数属性结合解构赋值,一次性完成属性声明与初始化:

interface TokenProps {
  index: number
  length: number
  op: string
  phrase: string
  quotes: 0 | 1 | 2
  wild: '' | '*'
}

export class Token implements TokenProps {
  constructor({
    index,
    length,
    op,
    phrase,
    quotes,
    wild
  }: TokenProps) {
    this.index = index
    this.length = length
    this.op = op
    this.phrase = phrase
    this.quotes = quotes
    this.wild = wild
  }

  // 其他方法
}

这种方式无需额外的基类或断言,但仍存在接口与解构参数的重复。


内容的提问来源于stack exchange,提问作者Aron Griffis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 09:03:13