Mobx 源码解读(三) Modifier

前面讨论 Observable 时,提到 Mobx 默认使用 deepEnhancer 来包装生成可观察的值,即递归地将该值的属性或元素(如果有)都转换成可观察的。Mobx 提供了五种类型的 Modifier,可以改变这种默认行为,本篇文章我们来看看 Modifier 的原理和实现。

基本原理

Modifier 可以作为 decorator、与 extendObservable 或 observable.object 一起使用,改变某一对象属性的默认转换行为。

比如:

1
2
3
let store = observable({
users: observable.shallow([])
});

使用 modifier 会将该属性包装成如下形式:

1
2
3
4
5
{
initialValue: [], // 属性值
enhancer: shallowEnhancer, // 不同 Modifier 使用不同的 enhancer
isMobxModifierDescriptor: true // 标志位,用于 defineObservablePropertyFromDescriptor 函数中的判断
}

这样,在 defineObservablePropertyFromDescriptor 时:

1
2
3
4
5
6
7
8
9
10
// 如果是 Modifier 的描述符,取出值和enhancer
if (isModifierDescriptor(descriptor.value)) {
const modifierDescriptor = descriptor.value as IModifierDescriptor<any>
defineObservableProperty(
adm,
propName,
modifierDescriptor.initialValue,
modifierDescriptor.enhancer
)
}

就能取出指定的 enhancer 去包装可观察的值了。

使用了 Modifier 的属性转换成 「可观察的值」后,其 enhancer 属性会将相应 enhancer 保存下来。下一次设置该属性值时,在 prepareNewValue 方法中将新值也使用 enhancer 去包装:

1
2
3
4
5
6
private prepareNewValue(newValue): T | IUNCHANGED {
// ......
// 应用 Modifier
newValue = this.enhancer(newValue, this.value, this.name)
return this.value !== newValue ? newValue : UNCHANGED
}

所以,Modifier 不光作用于「当前值」,对于将来设置的值也会生效。

五种类型的 Modifier

来看看五种 Modifier 的作用和实现原理。

observable.deep

这是默认的 Modifier,使用 deepEnhancer 对值进行包装。

deepEnhancer 我们已经很熟悉了,它会递归地在可观察对象的属性或可观察数组、Map 的元素上调用:

1
2
3
4
5
6
7
8
9
10
function deepEnhancer(v, _, name) {
if (isObservable(v)) return v

// observable 的这三个方法内部递归使用 deepEnhancer 创建可观察值
if (Array.isArray(v)) return observable.array(v, name)
if (isPlainObject(v)) return observable.object(v, name)
if (isES6Map(v)) return observable.map(v, name)

return v
}

observable.ref

referenceEnhancer 不对传入的值进行转换,直接返回:

1
2
3
function referenceEnhancer(newValue?) {
return newValue
}

observable.shallow

shallowEnhancer 只转换 Object, Array, Map 本身,不对其属性(或元素)转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function shallowEnhancer(v, _, name): any {
if (isModifierDescriptor(v))
fail(
"You tried to assign a modifier wrapped value to a collection, please define modifiers when creating the collection, not when modifying it"
)

if (v === undefined || v === null) return v
// 不对 Object, Array 和 Map 的属性(元素)进行处理
if (isObservableObject(v) || isObservableArray(v) || isObservableMap(v)) return v
// shallow 系列的方法使用 referenceEnhancer,不对属性(元素)进行转换
if (Array.isArray(v)) return observable.shallowArray(v, name)
if (isPlainObject(v)) return observable.shallowObject(v, name)
if (isES6Map(v)) return observable.shallowMap(v, name)

return fail(
"The shallow modifier / decorator can only used in combination with arrays, objects and maps"
)
}

observable.struct 和 observable.ref.struct

structEnhancer 表示可观察的「结构」,比如一个窗口大小对象,由长宽组成:

1
2
3
4
{
width: 100,
height: 100
}

structEnhancer 会使用 deepEqual 比较长宽是否发生变化,发生变化时才会通知观察者。即使窗口大小对象的引用发生变化而长宽值不变,也不会通知观察者。

structEnhancer 也分两种:deepStructEnhancer 和 refStructEnhancer,也就是说前者会在修饰属性值的属性上继续递归,将其转换成可观察的「结构」,而后者不会递归执行。

使用 structEnhancer 后,设置属性时,就可以看到 Enhancer 的第二个参数的作用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deepStructEnhancer(v, oldValue, name): any {
// set 时,深比较新旧值,如果相等,就不包装直接返回了
// 这样 prepareNewValue 中判断没有变化,就不会发送通知
if (deepEqual(v, oldValue)) return oldValue

if (isObservable(v)) return v

if (Array.isArray(v)) return new ObservableArray(v, deepStructEnhancer, name)
if (isES6Map(v)) return new ObservableMap(v, deepStructEnhancer, name)
if (isPlainObject(v)) {
const res = {}
asObservableObject(res, name)
extendObservableHelper(res, deepStructEnhancer, [v])
return res
}

return v
}

function refStructEnhancer(v, oldValue, name): any {
if (deepEqual(v, oldValue)) return oldValue
return v
}

作为 Decorator 使用的 Modifier

Modifier 经常作为 Decorator 使用,装饰一个属性:

1
2
3
4
5
class Message {
// 相当于 @observable.deep
@observable message = 'Hello world'
@observable.ref author = null
}

Mobx 对这种用法做了适配,以 observable.deep 为例。

我们知道,属性装饰器本质上就是一个参数与 Object.defineProperty 一致的函数,它返回一个新的属性描述符,并把这个新的描述符作用在目标属性上。因此 observable 和 observable.deep 中根据参数判断是否是作为属性装饰器使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createObservable(v: any = undefined) {
// observable 作为装饰器装饰某属性
// arguments[1] 为属性名,string 类型
if (typeof arguments[1] === "string") return deepDecorator.apply(null, arguments)
// ...
}

deep() {
// 作为函数调用,参数为需要包装的值
if (arguments.length < 2) {
return createModifierDescriptor(deepEnhancer, arguments[0]) as any

// 作为装饰器使用
} else {
return deepDecorator.apply(null, arguments)
}
}

其中 deepDecorator 由 createDecoratorForEnhancer 函数生成:

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
function createDecoratorForEnhancer(enhancer: IEnhancer<any>) {
return createClassPropertyDecorator(
(target, name, baseValue, _, baseDescriptor) => {
assertPropertyConfigurable(target, name)
invariant(!baseDescriptor || !baseDescriptor.get, getMessage("m022"))

// 先获取 admin 对象
const adm = asObservableObject(target, undefined)
// 然后在 admin 对象上使用指定 enhancer 定义属性即可
defineObservableProperty(adm, name, baseValue, enhancer)
},
function(name) {
const observable = this.$mobx.values[name]
if (
observable === undefined
)
return undefined
return observable.get()
},
function(name, value) {
setPropertyValue(this, name, value)
},
true,
false
)
}

createClassPropertyDecorator 是一个工具函数,主要为了解决 ts 和 Babel 在处理 Decorator 时的差异。该函数返回一个通用的装饰器函数,签名如下:

1
2
3
4
5
6
7
8
9
function classPropertyDecorator(
target: any,
key: string,
descriptor,
customArgs?: IArguments,
argLen: number = 0
) {
// ...
}

这个装饰器函数内部会先执行 createClassPropertyDecorator 传入的第一个函数,可以看到,这里传入的函数执行过程和第二篇中描述的在对象上定义可观察属性是一致的。然后这个装饰器函数内部会分别设置 get, set 访问器为第二、三个参数,可以看到,形式与 generateObservablePropConfig 中定义的也是一致的。