MobX中能否使用Mixin模式?如何在Mixin中标记Observable与Action?
在MobX中使用Mixin模式的解决方案
当然可以在MobX中使用Mixin模式!你遇到的问题主要有两个核心点:一是Mixin构造函数没有正确传递参数给基类,二是不知道如何在Mixin中安全地标记MobX的observable和action。下面是社区常用的几种解决方案:
问题根源分析
你的代码里Mixin的构造函数写了super()但没有传递age参数,而基类Animal的构造函数明确需要这个参数,这会直接导致实例化报错。另外,即使参数传递正确,在Mixin构造函数中调用makeObservable也需要注意和子类构造函数的配合——其实Mixin是可以有构造函数的,只要正确传递所有参数给父类就行。
方案1:Mixin中定义初始化方法,子类调用
把MobX的初始化逻辑从Mixin构造函数抽成一个独立方法,让子类在自己的构造函数中调用,这样可以彻底避免构造函数冲突:
import { autorun, makeObservable, observable, action } from "mobx"; type GConstructor<T = {}> = new (...args: any[]) => T; interface HasLife { grow: () => void; } class Animal { age: number; constructor(age: number) { this.age = age; } } function addLife<TBase extends GConstructor<Animal>>(Base: TBase) { return class Jumpable extends Base implements HasLife { // 定义MobX初始化方法 initMobX() { makeObservable(this, { age: observable, grow: action, }); } grow() { this.age++; } }; } class Dog extends addLife(Animal) implements HasLife { constructor(age: number) { super(age); // 调用Mixin的初始化方法 this.initMobX(); } } const dog = new Dog(0); autorun(() => { console.log(dog.age); }); dog.grow(); // 输出1,正常触发autorun
方案2:使用makeAutoObservable简化标记
makeAutoObservable会自动识别类中的可观察属性和动作,不需要手动标记,这样可以在Mixin的构造函数中安全调用,只要记得传递所有参数给父类:
import { autorun, makeAutoObservable } from "mobx"; type GConstructor<T = {}> = new (...args: any[]) => T; interface HasLife { grow: () => void; } class Animal { age: number; constructor(age: number) { this.age = age; } } function addLife<TBase extends GConstructor<Animal>>(Base: TBase) { return class Jumpable extends Base implements HasLife { // 正确传递所有参数给父类 constructor(...args: any[]) { super(...args); // 自动识别observable和action makeAutoObservable(this); } grow() { this.age++; } }; } class Dog extends addLife(Animal) implements HasLife { constructor(age: number) { super(age); } } const dog = new Dog(0); autorun(() => { console.log(dog.age); }); dog.grow(); // 正常输出1
这个方案是社区比较推荐的,因为它减少了手动标记的代码,同时避免了构造函数的冲突问题。
方案3:使用装饰器语法(需配置支持)
如果你的项目启用了TypeScript或Babel的装饰器支持,可以直接用@observable和@action装饰Mixin中的成员,不需要手动调用makeObservable:
首先在tsconfig.json中启用装饰器:
{ "compilerOptions": { "experimentalDecorators": true, "useDefineForClassFields": true } }
然后修改代码:
import { autorun, observable, action } from "mobx"; type GConstructor<T = {}> = new (...args: any[]) => T; interface HasLife { grow: () => void; } class Animal { @observable age: number; constructor(age: number) { this.age = age; } } function addLife<TBase extends GConstructor<Animal>>(Base: TBase) { return class Jumpable extends Base implements HasLife { @action grow() { this.age++; } }; } class Dog extends addLife(Animal) implements HasLife { constructor(age: number) { super(age); } } const dog = new Dog(0); autorun(() => { console.log(dog.age); }); dog.grow(); // 正常输出1
装饰器方案更简洁,但需要额外的配置,适合已经使用装饰器的项目。
总结
社区的通用解决方案主要围绕避免Mixin构造函数冲突和简化MobX标记逻辑展开:
- 如果你需要手动控制标记,优先用「初始化方法」方案;
- 追求简洁的话,
makeAutoObservable是最佳选择; - 项目已经支持装饰器的话,装饰器方案最直观。
内容的提问来源于stack exchange,提问作者Hoowong
相关产品推荐
相关产品推荐

