前言
最近一直在用Mobx开发中小型项目,开发起来真的,真的很爽,响应式更新,性能快,样板代码减少(相对Redux)。所以,想趁2019年结束前把Mobx源码研究一遍。
Tips
- 由于MobX的源码很大,因此只会把个人认为比较重要的部分截取说明
- 阅读的MobX源码版本@5.15.0
- 由于本人对TypeScript经验尚浅,所以我会将其编译成JavaScript阅读
- 下面会用mobx-source简称代替Mobx
如何调试源码
$ git clone https://github.com/mobxjs/mobx.git
$ cd mobx
$ cnpm i
- 查看
package.json
,发现执行脚本有quick-build
和small-build
,我选择的是small-build
,cnpm run small-build
然后在根目录下会生成.build.es5
和.build.es6
"scripts": {
"quick-build": "tsc --pretty",
"small-build": "node scripts/build.js"
},
- 把
.build.es6
改名为mobx-source
放到我写好的脚手架中
- 引入绝对路径
import { observable, action } from '../../mobx-source/mobx';
- 然后就可以愉快的调试源码了
function createObservable(v, arg2, arg3) {
debugger;
...
}
Demo
让我们从计数器开始,看看Mobx最基础的使用方式
React
@inject('counterStore')
@observer
class Index extends Component {
constructor(props) {
super(props);
}
render() {
const { counterStore } = this.props;
return (
count is: {counterStore.obj.count}
);
}
}
Mobx
import { observable, action } from '../../mobx-source/mobx';
class CounterStore {
@observable obj = {
count: 0
};
@action
add() {
this.obj.count++;
}
@action
reduce() {
this.obj.count--;
}
}
export default CounterStore;
界面如下
功能非常简单,实现也非常简单。通过observable
对count
进行了监听,只要count
产生了数据变化,就会自动刷新界面。那么,Mobx是如何做到的呢?让我们一步步来分析。
observable
首先,看入口文件,mobx-source -> mobx.js
,发现observable,action,runInAction
等其他方法都是从internal
引入的。
export { observable, action, runInAction } from "./internal";
打开internal.js
export * from "./api/action";
export * from "./api/autorun";
export * from "./api/observable";
然后看api/observable
这个文件,发现export const observable = createObservable;
function createObservable(v, arg2, arg3) {
// @observable someProp;
if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
return deepDecorator.apply(null, arguments);
}
// it is an observable already, done
if (isObservable(v))
return v;
// something that can be converted and mutated?
const res = isPlainObject(v)
? observable.object(v, arg2, arg3)
: Array.isArray(v)
? observable.array(v, arg2)
: isES6Map(v)
? observable.map(v, arg2)
: isES6Set(v)
? observable.set(v, arg2)
: v;
}
createObservable
主要做了以下几件事:
1、如果被观察的对象是string
或symbol
,那么执行deepDecorator.apply(null, arguments);
export const deepDecorator = createDecoratorForEnhancer(deepEnhancer);
deepEnhancer方法内部会判断当前修改的值类型,来走不同的工厂方法。
2、如果第一个参数已经是一个可被观察的对象,那么返回这个对象。
3、对第一个参数进行类型(object、array、map、set
)判断,然后调用不同的工厂方法。
const observableFactories = {
box(value, options) {
...
},
array(initialValues, options) {
...
},
map(initialValues, options) {
...
},
set(initialValues, options) {
...
},
object(props, decorators, options) {
...
},
ref: refDecorator,
shallow: shallowDecorator,
deep: deepDecorator,
struct: refStructDecorator
};
接下来,我们来分析createDecoratorForEnhancer
方法,主要有两个参数,第一个默认为true,第二个是个函数。res.enhancer = enhancer;
,会把上面传的deepEnhancer
,在此处进行挂载。根据变量不同类型,调用observable
的不同参数,如 object, array
来进行劫持。
export function createDecoratorForEnhancer(enhancer) {
invariant(enhancer);
const decorator = createPropDecorator(true, (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) => {
if (process.env.NODE_ENV !== "production") {
invariant(!descriptor || !descriptor.get, `@observable cannot be used on getter (property "${stringifyKey(propertyName)}"), use @computed instead.`);
}
const initialValue = descriptor
? descriptor.initializer
? descriptor.initializer.call(target)
: descriptor.value
: undefined;
asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
});
const res =
// Extra process checks, as this happens during module initialization
typeof process !== "undefined" && process.env && process.env.NODE_ENV !== "production"
? function observableDecorator() {
// This wrapper function is just to detect illegal decorator invocations, deprecate in a next version
// and simply return the created prop decorator
if (arguments.length < 2)
return fail("Incorrect decorator invocation. @observable decorator doesn't expect any arguments");
return decorator.apply(null, arguments);
}
: decorator;
res.enhancer = enhancer;
return res;
}
createPropDecorator
方法创建属性拦截器,addHiddenProp
方法为目标对象添加Symbol(mobx pending decorators)
属性。
export function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) {
return function decoratorFactory() {
let decoratorArguments;
const decorator = function decorate(target, prop, descriptor, applyImmediately
// This is a special parameter to signal the direct application of a decorator, allow extendObservable to skip the entire type decoration part,
// as the instance to apply the decorator to equals the target
) {
...
if (!Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) {
const inheritedDecorators = target[mobxPendingDecorators];
addHiddenProp(target, mobxPendingDecorators, Object.assign({}, inheritedDecorators));
}
target[mobxPendingDecorators][prop] = {
prop,
propertyCreator,
descriptor,
decoratorTarget: target,
decoratorArguments
};
return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable);
};
};
}
由于上面我定义的变量是对象,所以Mobx会把这个对象拦截,执行observableFactories.object
,
object(props, decorators, options) {
if (typeof arguments[1] === "string")
incorrectlyUsedAsDecorator("object");
const o = asCreateObservableOptions(options);
if (o.proxy === false) {
return extendObservable({}, props, decorators, o);
}
else {
const defaultDecorator = getDefaultDecoratorFromObjectOptions(o);
const base = extendObservable({}, undefined, undefined, o);
const proxy = createDynamicObservableObject(base);
extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
return proxy;
}
},
asCreateObservableOptions
创建一个可观察的对象,由于已经是object了,所以proxy为undefined,则进else, const base = extendObservable({}, undefined, undefined, o);
加工处理下o对象,转成Symbol数据类型,然后看createDynamicObservableObject
,很关键的方法,这个函数内部就是利用Proxy
来创建拦截器,对这个对象的属性has, get, set, deleteProperty, ownKeys,preventExtensions
方法进行了代理拦截。
export function createDynamicObservableObject(base) {
const proxy = new Proxy(base, objectProxyTraps);
base[$mobx].proxy = proxy;
return proxy;
}
const objectProxyTraps = {
has(target, name) {
...
},
get(target, name) {
...
},
set(target, name, value) {
...
},
deleteProperty(target, name) {
...
},
ownKeys(target) {
...
},
preventExtensions(target) {
...
}
};
extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
,会对对象属性遍历,来创建拦截器,而且这里面会牵扯到一个事务的概念,后面会分析事务。
博客
欢迎关注博客
最后
相关代码已上传至react-scaffolding-mobx