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

TypeScript如何为对象动态添加方法?StoreBuilder返回commands访问报错如何解决

问题原因

TypeScript无法自动追踪你通过add方法动态挂载到commands对象上的属性,原build方法返回的commands被推导为最宽泛的Object类型,没有具体的属性签名,所以访问addCoin、takeCoin时会抛出类型错误。

解决方案

方案1:修改StoreBuilder类,通过泛型自动收集命令类型(推荐)

给StoreBuilder增加第二个泛型参数,用来累积所有通过add方法注册的命令名和对应的参数类型,TS会在你每次调用add时自动更新泛型状态,最终build返回的commands就会有完整的类型提示,无需额外手动声明类型。

// 先定义通用Command类型
type Command<TArg> = (arg: TArg) => { type: string; payload: TArg }

// 新增第二个泛型参数TCommands,默认是空对象,用于累积注册的命令
class StoreBuilder<TStore, TCommands extends Record<string, Command<any>> = {}> {
  private _map: Map<string, (store: TStore, payload: any) => void>
  private _selectors: Map<string, Command<any>>
  
  constructor(){
    this._map = new Map()
    this._selectors =  new Map()
  }

  // add方法返回更新了TCommands的StoreBuilder实例,自动累积类型
  add<TArg, TKind extends string>(kind: TKind, code: (s:TStore, x:TArg)=> void): StoreBuilder<TStore, TCommands & Record<TKind, Command<TArg>>> {
    if(!this._map.has(kind)){
      const func = (store: TStore, a0: TArg)=>{
          code(store, a0)
      }
      this._map.set(kind, func)
    }

    if(!this._selectors.has(kind)){
      const func = (arg: TArg)=>{
        return {type: kind, payload: arg}
      }
      this._selectors.set(kind, func)
    }
    return this as any
  }

  build(){
    const reducer = (store: TStore, action: {type: string, payload: any})=>{
      if(this._map.has(action.type)){
       try {
        const code = this._map.get(action.type)!
        code(store, action.payload)
       } catch (err) {
         console.error(err)
       }
      }
    }

    // 直接断言为累积的TCommands类型
    const commands = {} as TCommands
    for(const [name, func] of this._selectors){
      commands[name as keyof TCommands] = func
    }

    return { commands, reducer }
  }
}

修改后原有使用逻辑完全不需要改动,TS会自动识别commands上的所有方法和参数类型:

type mystoretype ={
  coins: number
}

const coiner = new StoreBuilder<mystoretype>()

coiner.add<number>('addCoin', (s,x)=>{
  s.coins += x
})

coiner.add<number>('takeCoin', (s,x)=>{
  if(s.coins>x){
    s.coins-= x
  }
})

const {commands, reducer} = coiner.build()
// 以下代码不会再报类型错误,还会有参数类型提示
const cmd1 = commands.addCoin(23)
const cmd2 = commands.takeCoin(34)

方案2:手动断言commands类型(临时方案)

如果不想修改原有类的定义,可以手动给build返回值做类型断言,适合小场景快速修复:

// 手动声明你注册的命令类型
type MyCommands = {
  addCoin: (num: number) => {type: 'addCoin', payload: number}
  takeCoin: (num: number) => {type: 'takeCoin', payload: number}
}

// 解构时做类型断言
const {commands, reducer} = coiner.build() as {commands: MyCommands, reducer: (store: mystoretype, action: any) => void}

// 后续调用不会报错
const cmd1 = commands.addCoin(23)
const cmd2 = commands.takeCoin(34)

内容的提问来源于stack exchange,提问作者Simple Fellow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 20:15:02