mobx-react(5.4.4)源码解读: observer

1,676 阅读3分钟

前言

在看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的调用栈,简单来说就是两点:

  1. 设置globalState.trackingDerivation = derivation(在进行get拦截时会使用)
  2. 回调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