前言
在看mobx-react源码之前,先要熟悉mobx中observable、autorun、修改属性值触发回调要有一定的了解,可以先仔细看一下mobx源码解读系列。
正文开始
基于上文中用到例子,进行react、mobx-react环境搭建后,再来看一个简单的demo
import React from 'react';
import { observable, autorun } from 'mobx';
import { observer } from 'mobx-react';
const vipMember = observable({
name: 'Susan',
age: 18,
points: 0,
});
@observer
class App extends React.Component {
constructor(props) {
super(props)
}
handleAdd() {
vipMember.points++
}
render() {
return (
<div>
<div>
<span>{`${vipMember.name} has ${vipMember.points} points.`}</span>
<button onClick={this.handleAdd.bind(this)}>+</button>
</div>
</div>
)
}
}
export default App
功能非常简单,一个label显示有多少积分,一个"+"按钮具有自增的功能,点击"+"按钮,label中的积分发生变化。
代码也非常简单:
(1)定义一个observable对象vipMember (2)定义一个被装饰器observer包装的类App
对于observable对象的封装原理,请参考这里,接下来我们来看看mobx-react的observer方法的运行原理。
为了方便调试,mobx-react安装的是5.4.4(题外话:还支持componentWillReact的生命周期,好像是v6开始就不支持了)。打开node_modules/mobx-react/index.module.js找到observer方法
function observer(arg1, arg2) {
// 一些参数的判断逻辑
...
var componentClass = arg1;
...
var target = componentClass.prototype || componentClass;
mixinLifecycleEvents(target);
componentClass.isMobXReactObserver = true;
makeObservableProp(target, "props");
makeObservableProp(target, "state");
var baseRender = target.render;
target.render = function () {
return makeComponentReactive.call(this, baseRender);
};
return componentClass;
}
可以看到将一个方法赋值给了类的原型对象的render方法,简而言之就是对组件的render方法进行了包装,同时将原来的render方法作为参数baseRender传递给makeComponentReactive。
接下来重点来看看makeComponentReactive方法
function makeComponentReactive(render) {
var baseRender = render.bind(this);
var isRenderingPending = false;
var reaction = new Reaction("???"), function () {
if (!isRenderingPending) {
isRenderingPending = true;
...
try {
setHiddenProp(_this2, isForcingUpdateKey, true);
if (!_this2[skipRenderKey]) Component.prototype.forceUpdate.call(_this2);
hasError = false;
} finally {
setHiddenProp(_this2, isForcingUpdateKey, false);
if (hasError) reaction.dispose();
}
}
}
function reactiveRender() {
...
reaction.track(function () {
...
try {
rendering = _allowStateChanges(false, baseRender);
} catch (e) {
exception = e;
}
...
});
return rendering;
}
return reactiveRender.call(this);
}
拆分代码逻辑
(1)设置render方法的this指向
(2)创建一个Reaction实例,其中reaction.onInvalidate为Component.prototype.forceUpdate
(3)定义一个reactiveRender方法,该方法调用reaction的track方法,并且return rendering
(4)return reactive.call(this)
所以找到方法入口:reactive,reactive函数体内会去调用reaction的track方法,在这篇文章中,我们已经介绍过了track的调用栈,简单来说就是两点:
- 设置globalState.trackingDerivation = derivation(在进行get拦截时会使用)
- 回调track方法传入的function。
假设现在已经完成了track的方法调用,开始回调执行track方法传入的function,其中rendering = _allowStateChanges(false, baseRender);,跟踪到mobx的allowStateChanges方法
function allowStateChanges(allowStateChanges, func) {
var prev = allowStateChangesStart(allowStateChanges);
var res;
try {
res = func();
}
finally {
allowStateChangesEnd(prev);
}
return res;
}
执行baseRender方法,这就完成了App组件render方法的第一次调用。
在render方法中,我们看到有对vipMember对象 name和points属性的读取,从而进入到observers的收集流程,mobx(5.14.0)源码解读二 autorun的第二点:**执行autorun回调的第一次调用后,get拦截器又是如何运行的?**有介绍,不清楚的可以回头再看一下。
这时候我们就完成了依赖收集,后续对vipMember属性的设置,就涉及到mobx(5.14.0)源码解读三 observable属性变更如何触发autorun回调这篇文章的内容,简单来说的调用栈如下:proxy.handler.set -> ObservableObjectAdministration.prototype.write ->ObservableValue.prototype.setNewValue->Atom.prototype.reportChanged->遍历observableValue.observers即reactions-> Reaction.prototype.schedule->Reaction.prototype.runReaction->reaction.onInvalidate
这就触发了之前在new Reaction时传入的第二个参数,
function () {
if (!isRenderingPending) {
isRenderingPending = true;
if (typeof _this2.componentWillReact === "function") _this2.componentWillReact();
if (_this2[mobxIsUnmounted] !== true) {
var hasError = true;
try {
setHiddenProp(_this2, isForcingUpdateKey, true);
if (!_this2[skipRenderKey]) Component.prototype.forceUpdate.call(_this2);
hasError = false;
} finally {
setHiddenProp(_this2, isForcingUpdateKey, false);
if (hasError) reaction.dispose();
}
}
}
}
调用Component.prototype.forceUpdate.call(_this2)方法,触发react页面重新渲染。
总结
observer主要做了以下几件事:
(1)执行reaction.track,通过回调去调用react的render方法,完成依赖收集
(2)修改属性值,触发onInvalidate,通过调用forceUpdate完成页面的重新渲染,所以被observer包装的react组件,会掉过shouldComponentUpdate返回值的约束,始终会重新render