Mobx简介

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

Mobx核心概念

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

Mobx数据流

Mobx数据流

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

Mobx observer

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

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

使状态可观测

  1. observable
  2. makeObservable
  3. makeAutoObservable

注:

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 函数式
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 基本
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!