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
相关产品推荐
相关产品推荐

