前面讨论 Observable 时,提到 Mobx 默认使用 deepEnhancer 来包装生成可观察的值,即递归地将该值的属性或元素(如果有)都转换成可观察的。Mobx 提供了五种类型的 Modifier,可以改变这种默认行为,本篇文章我们来看看 Modifier 的原理和实现。
基本原理
Modifier 可以作为 decorator、与 extendObservable 或 observable.object 一起使用,改变某一对象属性的默认转换行为。
比如:
1 | let store = observable({ |
使用 modifier 会将该属性包装成如下形式:
1 | { |
这样,在 defineObservablePropertyFromDescriptor 时:
1 | // 如果是 Modifier 的描述符,取出值和enhancer |
就能取出指定的 enhancer 去包装可观察的值了。
使用了 Modifier 的属性转换成 「可观察的值」后,其 enhancer 属性会将相应 enhancer 保存下来。下一次设置该属性值时,在 prepareNewValue 方法中将新值也使用 enhancer 去包装:
1 | private prepareNewValue(newValue): T | IUNCHANGED { |
所以,Modifier 不光作用于「当前值」,对于将来设置的值也会生效。
五种类型的 Modifier
来看看五种 Modifier 的作用和实现原理。
observable.deep
这是默认的 Modifier,使用 deepEnhancer 对值进行包装。
deepEnhancer 我们已经很熟悉了,它会递归地在可观察对象的属性或可观察数组、Map 的元素上调用:
1 | function deepEnhancer(v, _, name) { |
observable.ref
referenceEnhancer 不对传入的值进行转换,直接返回:
1 | function referenceEnhancer(newValue?) { |
observable.shallow
shallowEnhancer 只转换 Object, Array, Map 本身,不对其属性(或元素)转换:
1 | function shallowEnhancer(v, _, name): any { |
observable.struct 和 observable.ref.struct
structEnhancer 表示可观察的「结构」,比如一个窗口大小对象,由长宽组成:
1 | { |
structEnhancer 会使用 deepEqual 比较长宽是否发生变化,发生变化时才会通知观察者。即使窗口大小对象的引用发生变化而长宽值不变,也不会通知观察者。
structEnhancer 也分两种:deepStructEnhancer 和 refStructEnhancer,也就是说前者会在修饰属性值的属性上继续递归,将其转换成可观察的「结构」,而后者不会递归执行。
使用 structEnhancer 后,设置属性时,就可以看到 Enhancer 的第二个参数的作用了:
1 | function deepStructEnhancer(v, oldValue, name): any { |
作为 Decorator 使用的 Modifier
Modifier 经常作为 Decorator 使用,装饰一个属性:
1 | class Message { |
Mobx 对这种用法做了适配,以 observable.deep 为例。
我们知道,属性装饰器本质上就是一个参数与 Object.defineProperty 一致的函数,它返回一个新的属性描述符,并把这个新的描述符作用在目标属性上。因此 observable 和 observable.deep 中根据参数判断是否是作为属性装饰器使用:
1 | function createObservable(v: any = undefined) { |
其中 deepDecorator 由 createDecoratorForEnhancer 函数生成:
1 | function createDecoratorForEnhancer(enhancer: IEnhancer<any>) { |
createClassPropertyDecorator 是一个工具函数,主要为了解决 ts 和 Babel 在处理 Decorator 时的差异。该函数返回一个通用的装饰器函数,签名如下:
1 | function classPropertyDecorator( |
这个装饰器函数内部会先执行 createClassPropertyDecorator 传入的第一个函数,可以看到,这里传入的函数执行过程和第二篇中描述的在对象上定义可观察属性是一致的。然后这个装饰器函数内部会分别设置 get, set 访问器为第二、三个参数,可以看到,形式与 generateObservablePropConfig 中定义的也是一致的。