Mobx与Vue响应式对比

Mobx是一个通过透明的函数响应式编程的状态管理库,一提到响应式,首先想到的就是如今响应式编程的代表Vue。两个在响应式的实现都选择了数据劫持的方法,就是将赋值的过程借助函数处理,实现一些额外的操作,比如记录日志,触发回调等等。Vue通过Object.defineProperty,Mobx同样也使用了Object.defineProperty(最新的Mobx5使用了Proxy实现),不同的是Mobx在这里只是用作创建代理对象,真正的响应式实现在observable实例上。

以下分析涉及代码为mobx4版本。

响应式数据

Mobx中,将响应式数据按类型可以分为,基本类型,Object,Array,Map。Object,Array,Map都是在基本类型的基础上的递归实现。其中几个关键类,ObservableValue继承自Atom类,是基本类型的响应式实现,提供了set/get方法。ObservableObjectAdministration实例作为代理,能将基本数据类型的赋值动作转变成响应式,提供了write/read方法,Mobx在对象上创建了一个ObservableObjectAdministration的实例$mobx代理,减少了对源数据的污染,通过Object.defineProperty,将对象的操作,委托给代理。ObservableArrayAdministration 实例能处理数组;ObservableMap 实例能处理 map 数据结构。

前端知识图谱

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
// .api/observable.ts
// mobx提供的公共api
const observableFactories: IObservableFactories = {
box<T>(value?: T, options?: CreateObservableOptions): IObservableValue<T> {
return new ObservableValue(value, getEnhancerFromOptions(o), o.name)
},
shallowBox<T>(value?: T, name?: string): IObservableValue<T> {
return observable.box(value, { name, deep: false })
},
array<T>(initialValues?: T[], options?: CreateObservableOptions): IObservableArray<T> {
return new ObservableArray(initialValues, getEnhancerFromOptions(o), o.name) as any
},
shallowArray<T>(initialValues?: T[], name?: string): IObservableArray<T> {
return observable.array(initialValues, { name, deep: false })
},
map<K, V>(
initialValues?: IObservableMapInitialValues<K, V>,
options?: CreateObservableOptions
): ObservableMap<K, V> {
return new ObservableMap<K, V>(initialValues, getEnhancerFromOptions(o), o.name)
},
shallowMap<K, V>(
initialValues?: IObservableMapInitialValues<K, V>,
name?: string
): ObservableMap<K, V> {
return observable.map(initialValues, { name, deep: false })
},
object<T>(
props: T,
decorators?: { [K in keyof T]: Function },
options?: CreateObservableOptions
): T & IObservableObject {
return extendObservable({}, props, decorators, o) as any
},
shallowObject<T>(props: T, name?: string): T & IObservableObject {
if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("shallowObject")
deprecated(`observable.shallowObject`, `observable.object(values, {}, { deep: false })`)
return observable.object(props, {}, { name, deep: false })
}
} as any
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
51
52
53
54
55
56
57
58
// ./types/observablevalue.ts
export class ObservableValue<T> extends Atom
implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
hasUnreportedChange = false
interceptors
changeListeners
value
dehancer: any
public set(newValue: T) {
const oldValue = this.value
newValue = this.prepareNewValue(newValue) as any
if (newValue !== UNCHANGED) {
const notifySpy = isSpyEnabled()
if (notifySpy) {
spyReportStart({
type: "update",
name: this.name,
newValue,
oldValue
})
}
this.setNewValue(newValue)
if (notifySpy) spyReportEnd()
}
}
private prepareNewValue(newValue): T | IUNCHANGED {
checkIfStateModificationsAreAllowed(this)
if (hasInterceptors(this)) {
const change = interceptChange<IValueWillChange<T>>(this, {
object: this,
type: "update",
newValue
})
if (!change) return UNCHANGED
newValue = change.newValue
}
// apply modifier
newValue = this.enhancer(newValue, this.value, this.name)
return this.value !== newValue ? newValue : UNCHANGED
}
setNewValue(newValue: T) {
const oldValue = this.value
this.value = newValue
// 通知变化
this.reportChanged()
}
public get(): T {
// 收集依赖
this.reportObserved()
}
}
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// ./types/observableobject.ts
export function addHiddenFinalProp(object: any, propName: string, value: any) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: false,
configurable: true,
value
})
}
export class ObservableObjectAdministration
implements IInterceptable<IObjectWillChange>, IListenable {
values: { [key: string]: ObservableValue<any> | ComputedValue<any> } = {}
keys: undefined | IObservableArray<string>
changeListeners
interceptors
constructor(public target: any, public name: string, public defaultEnhancer: IEnhancer<any>) {}
read(owner: any, key: string) {
if (this.target !== owner) {
this.illegalAccess(owner, key)
return
}
return this.values[key].get()
}
write(owner: any, key: string, newValue) {
const instance = this.target
const observable = this.values[key]
newValue = (observable as any).prepareNewValue(newValue)
if (newValue !== UNCHANGED) {
const notify = hasListeners(this)
const notifySpy = isSpyEnabled()
const change =
notify || notifySpy
? {
type: "update",
object: instance,
oldValue: (observable as any).value,
name: key,
newValue
}
: null
if (notifySpy) spyReportStart({ ...change, name: this.name, key })
;(observable as ObservableValue<any>).setNewValue(newValue)
if (notify) notifyListeners(this, change)
if (notifySpy) spyReportEnd()
}
}
}
export function asObservableObject(
target,
name: string = "",
defaultEnhancer: IEnhancer<any> = deepEnhancer
): ObservableObjectAdministration {
let adm = (target as any).$mobx
if (adm) return adm
adm = new ObservableObjectAdministration(target, name, defaultEnhancer)
addHiddenFinalProp(target, "$mobx", adm)
return adm
}
export function generateObservablePropConfig(propName) {
return (
observablePropertyConfigs[propName] ||
(observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get() {
return this.$mobx.read(this, propName)
},
set(v) {
this.$mobx.write(this, propName, v)
}
})
)
}

数组和Map的具体实现就不在这里介绍,具体可到源码中查看。

运行机制

在严格模式下Mobx的一次响应式过程包括,从执行动作 action 促使响应式数据 observable 变化,再到衍生 derivation 执行的整个过程。

  1. observable: 响应式数据。
  2. derivation: 衍生,响应式数据变更后执行的副作用函数,包含计算属性、反应,可以类比Vue中的watch。
  3. action: 动作,由其促使响应式数据发生变更。action的意义在于,使用事务封装执行动作,将一组响应式数据变更复合为一,等到这组响应式数据变更完结后,才执行 derivation。

observable

继承自Atom类额外增加了reportObservereportChanged方法。reportObserve注册依赖,即当observable被观察时,类比Vue中depend;当observable数据变更时,会调用reportChanged方法,类比Vue中notify

derivation

derivation即消费observable的观察者,在 mobx 实现中,observable, derivation 相互持有彼此的引用,当观察数据变更时,derivation将执行。

执行过程

在Vue中,注册的回调函数执行是通过内部的异步队列控制的,避免不必要的性能损耗,Mobx则通过事务管理更新操作。Mobx默认更新是同步的,obserable一旦发生变化,derivation就会执行。如果要是希望通线程中更新操作只触发一次,那直接使用action,将操作包裹起来就行,而实现的核心就是事务的运用。

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//./core/atom.ts
class AtomImpl implements IAtom {
public reportChanged() {
startBatch()
propagateChanged(this)
endBatch()
}
}
//./core/observable.ts
/**
* 开启事务
*/
function startBatch() {
globalState.inBatch++
}
/**
* 结束事务
*/
function endBatch() {
if (--globalState.inBatch === 0) {
runReactions()
const list = globalState.pendingUnobservations
for (let i = 0; i < list.length; i++) {
const observable = list[i]
observable.isPendingUnobservation = false
if (observable.observers.size === 0) {
if (observable.isBeingObserved) {
observable.isBeingObserved = false
observable.onBecomeUnobserved()
}
if (observable instanceof ComputedValue) {
observable.suspend()
}
}
}
globalState.pendingUnobservations = []
}
}
/**
* 将derivation加入队列
*/
function propagateChanged(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return
observable.lowestObserverState = IDerivationState.STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.onBecomeStale()
}
d.dependenciesState = IDerivationState.STALE
})
}
function propagateMaybeChanged(observable: IObservable) {
if (observable.lowestObserverState !== IDerivationState.UP_TO_DATE) return
observable.lowestObserverState = IDerivationState.POSSIBLY_STALE
observable.observers.forEach(d => {
if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
d.dependenciesState = IDerivationState.POSSIBLY_STALE
d.onBecomeStale()
}
})
}
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
//./core/action.ts
function createAction(actionName: string, fn: Function): Function & IAction {
const res = function() {
return executeAction(actionName, fn, this, arguments)
}
}
function executeAction(actionName: string, fn: Function, scope?: any, args?: IArguments) {
const runInfo = startAction(actionName, fn, scope, args)
try {
return fn.apply(scope, args)
} finally {
endAction(runInfo)
}
}
function startAction(
actionName: string,
fn: Function,
scope: any,
args?: IArguments
): IActionRunInfo {
const prevDerivation = untrackedStart()
startBatch() // 开启事务
const prevAllowStateChanges = allowStateChangesStart(true)
return {
prevDerivation,
prevAllowStateChanges,
notifySpy,
startTime
}
}
function endAction(runInfo: IActionRunInfo) {
allowStateChangesEnd(runInfo.prevAllowStateChanges)
endBatch() // 关闭事务
untrackedEnd(runInfo.prevDerivation)
}

Mobx中的事务通过 globalState.inBatch 计数器标识:startBatch阶段,globalState.inBatch加 1;endBatch阶段,globalState.inBatch减 1;当globalState.inBatch为 0 时,表示单个事务周期结束,事务的意义就在于将并行的响应式数据变更视为一组,在一组变更完成之后,才执行相应的衍生,这样就保证了同步更新,但衍生只执行一次。

最后

除了上述提到的响应式实现不同,Mobx和Vue在响应数据和衍生的绑定关系上也不一样。在Vue中,衍生只用三种,computedwatch以及render。而Mobx种提供了computedautorunwhen,reactionobserver。由于在Vue中响应式只是他的一部分,需要配合组件系统使用,所以数据和衍生的绑定是在内部实现的,可以理解成自动绑定依赖。Mobx提供了更加细粒度的调用,因此绑定的建立也更严格,需要满足:

  • “读取” 是对象属性的间接引用,可以用过 .或者 []的形式完成。
  • “追踪函数”computed 表达式、observer 组件的 render() 方法和 whenreactionautorun 的第一个入参函数。
  • “过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。