Neil.

Mobx

Mobx简介

  • 非单一数据源
  • 单向数据流

Mobx核心概念

  • State
  • Action
  • Derivation
    • Computed value:是一个无参纯函数,输入相同时将直接返回上次缓存的值
    • Reaction:当State改变时,需要执行的副作用

Mobx数据流

Mobx数据流

通过action改变state,进而触发derivation更新

Mobx observer

observer是一个HOC,当可观测State改变时负责重渲染传入的组件

export default observer(function MyComponent({someObservableState}) => {
    // ...
})

使状态可观测

  1. observable
  2. makeObservable
  3. makeAutoObservable

注:

  • observable用于不能使原始值、实例变为可观测;
  • observable作为函数可单独使用,也可以在类中作为注解或make*第二参数中的标记使用;
  • makeObservable和makeAutoObservable只能在类中使用;
  • makeAutoObservable不能在子类中使用;

例子:

// 函数式
const store = observable({
    value: 1
});
const increase = action(() => {
    store.value++;
});

// 注解(装饰器语法,截至2023年5月25日,尚未成为ES标准)
class Store {
    @observable value
    @action (v) => {
        this.value = v;
    }
}

// makeObservable
class Doubler {
    value
    constructor(value) {
        makeObservable(this, {
            value: observable,
            double: computed,
            increment: action,
        })
        this.value = value
    }
    get double() {
        return this.value * 2
    }
    increment() {
        this.value++
    }
}

// makeAutoObservable
class Doubler {
    value
    constructor(value) {
        // 更方便、简洁
        makeObservable(this)
        this.value = value
    }
    get double() {
        return this.value * 2
    }
    increment() {
        this.value++
    }
}

observable与make*的区别

  • observable不改变传入的对象,而是克隆该对象再转换为可观察对象;make*改变传入的对象,直接将其转换为可观察对象;
  • observable使用Proxy实现对象代理,make*

Mobx action

  • action是改变可观察state的唯一方式;
  • action是纯函数;
  • action在transactions内执行,因而是同步的,期间不完整的state是不能获取到的(原子性);
  • 嵌套的action会合并在一个事务中;
  • 平行的action会各自产生一个事务;
  • 只有定义在原型上的action可被子类覆盖;

action.bound用于正确绑定this指针,避免字面量方式定义的对象在使用时this指向错误对象,可以代替箭头函数;

Mobx 异步action

  1. 基本方式:异步操作的回调中需要改变state时,要包装为action;
  2. runInAction方式:在runInAction中改变state,是基本方式的简化;
  3. flow方式:将异步操作定义在generator function中,并将其标记为flow,mobx会负责generator的自动执行,并将其包装成Promise;
// 基本
async fetchA () {
    const data = await fetch(...);
    action(() => {
        this.value = data;
    })();
}

// runInAction
async fetchA () {
    const data = await fetch(...);
    runInAction(() => {
        this.value = data;
    });
}

// flow
@flow
* fetchA() {
    const data = yield fetch(...);
    try {
        this.value = data;
    } catch() {
        //...
    }
}

注:

  • makeAutoObservable会自动将生成器函数标记为flow;
  • flow返回的Promise被添加了一个cancel(),用于取消生成器的执行,但try、finally仍会执行;

Mobx computed value

  • 惰性求值;
  • 输出会被缓存,依赖的可观察对象改变时,才会重新求值;
  • 未被观察时将暂时停用;

computed做注解或函数使用时都可以传入options对象,其中比较有价值的一个属性是equals,用于控制如何比较输出是否相等,除了自定义比较函数以外,mobx内置了四种比较方式:

  1. comparer.identity,即全等号===
  2. comparer.default,除了NaN等于NaN,其他与comparer.identity相同;
  3. comparer.shallow,浅比较;
  4. comparer.structural,结构比较;

其余属性:

  1. name,用于debug时提供一个有意义的标识;
  2. requiresReaction,为昂贵计算设为true;
  3. keepAlive,设为true则未被观察时也不会停用;

注:2、3实际应用场景暂不清楚。

Mobx reaction

可观测对象变化时,reaction用于执行副作用,简单来说就是执行一些不纯的函数,例如日志打印。

  1. autorun((reaction) => effect, options?)
  2. reaction(() => data, (data, preData, reaction) => effect, options?)
  3. when
    1. when(predicate: () => boolean, effect?: () => void, options?)
    2. when(predicate: () => boolean, options?): Promise

以上三个api都会监测可观测对象,当可观测对象改变时执行副作用,并各自提供了一种方式清理掉副作用;

总结一下Reaction就是以某种方式实现三种行为:

  1. 监测(或订阅、消费)可观测对象;
  2. 可观测对象改变时执行副作用;
  3. 清理副作用;

autorun,第一个参数实现1和2,该参数的参数是一个reaction对象,reaction.dispose实现3,会在初始时执行一次

reaction,第一个参数实现1,第二个参数实现2,第二参数的reaction参数实现3;

when,第一个参数实现1和3,predicate的Boolean返回值会被监测,返回true时自动执行并清理副作用,第二个参数实现2,未提供时返回一个Promise,通过Promise.cancel手动清理副作用;

根据执行时机的不同:

  • autorun初始时执行一次,之后每当观测值改变都将再次执行;
  • when在条件为true时执行一次副作用;
  • reaction初始时不执行,之后每当观测值改变都将再次执行;

注:Reactions和Computed Value的一个重要区别就是是否产生新的值,避免滥用Reactions!