使用MobX将Decimal类实例作为observable属性时调用方法报未定义错误
报错触发的两类核心原因
- 组件缺少空值兜底逻辑
Store中value字段的初始值为null,组件未做任何空值判断就直接链式调用mul方法。如果首次渲染时setValue逻辑尚未执行(比如对应交互、异步流程没触发),就会因为值为空导致调用方法失败。 - MobX的深度可观察逻辑破坏了Decimal实例的原型链
这是搭配使用MobX和decimal.js时最容易踩的坑:MobX 6+版本默认对observable标记的字段开启深度观察,当把new Decimal(100)的实例赋值给观察字段时,MobX会递归遍历实例的自有属性,将其转换为普通可观察代理对象。这个代理对象只保留遍历到的自有可枚举属性,不会继承Decimal原型上挂载的mul/add/toFixed等方法,最终导致取到的value是没有计算方法的普通对象,调用mul时就会抛出_value.mul is not a function的特定报错(因为此时value并非null,是被篡改后的普通对象)。
可落地的修复方案
- 为Decimal类型的观察字段关闭深度代理
将value的观察类型从默认的深度observable改为observable.ref,告知MobX仅对字段的重新赋值动作做响应,不要对字段内部的对象结构做递归代理转换,完整保留Decimal实例的原型链和所有内置方法。修改后的Store构造函数代码:constructor() { makeObservable(this, { // 替换原有的 observable 配置 value: observable.ref, setValue: action, }); } - 补全组件层面的空值判断
避免在值为null的初始渲染阶段直接调用实例方法,增加前置分支判断,等Decimal实例完成赋值后再做计算渲染。修改后的组件逻辑:const MyPage = observer(() => { const value = useStoreValue((rootStore) => rootStore.myStore.value); // 空值阶段先渲染占位内容 if (!value) return <span>--</span>; // 确保拿到完整Decimal实例后再调用计算方法 return <span>{value.mul(5).toString()}</span>; }); - 特殊场景适配
如果确实需要观察Decimal实例内部的属性变化(绝大多数高精度数值场景下不需要,因为Decimal实例本身是不可变值,每次计算都会生成新实例),可以在MobX的可观察配置中自定义对象转换规则,跳过对Decimal类实例的代理处理,避免原型链丢失。
内容的提问来源于stack exchange,提问作者Naor
相关产品推荐
相关产品推荐

