React源码解析

  • 生命周期

 当首次挂载组件时,按顺序执行 getDefaultProps、getInitialState、componentWillMount、 render 和 componentDidMount。
 当卸载组件时,执行 componentWillUnmount。
 当重新挂载组件时,此时按顺序执行 getInitialState、componentWillMount、render 和 componentDidMount,但并不执行 getDefaultProps。
 当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps、 shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate

  1. 创建自定义组件

class MyComponent extends React.Component 其实就 是调用内部方法 createClass 创建组件
调用getDefaultProps。
由于 getDefaultProps 是通过构造函数进行管理的,所以也是整个生命周期中先开始执行的

  1. MOUNTING(加载组件)

若存在 componentWillMount,则执行。如果此时在 componentWillMount 中调用 setState 方法,不会触发re-render,会进行state合并,并且this.state不是最新的。

  1. RECEIVE_PROPS(更新组件)

如果此时在 componentWillReceiveProps 中调 用 setState,是不会触发 re-render的,而是会进行 state 合并。
updateComponent 本质上也是通过递归渲染内容的,由于递归的特性,父组件的 component- WillUpdate 是在其子组件的 componentWillUpdate 之前调用的,而父组件的 componentDidUpdate 也是在其子组件的 componentDidUpdate 之后调用的
禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会造成循环调用,直至耗光浏览器内存后崩溃

  1. UNMOUNTING(卸载组件)

如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如 果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新 队列和更新状态都被重置为 null

无状态组件
只有一个render方法


  • setState

调用setState -> 新state进入队列 -> 合并更新队列 -> 判断是否在批量更新 -> 如果在,component放入dirtyComponent等待下一次更新;如果不在,进行批量更新

ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState); //更新state
  if (callback) {//回调函数
    this.updater.enqueueCallback(this, callback, 'setState');
  }
 };

enqueueSetState中,合并更新队列,调用enqueueUpdate

function enqueueUpdate(component) {
  // 不处于批量更新模式,进行更新
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component); //批处理更新
    return;
  }
  // 处于批量更新模式
  dirtyComponents.push(component);
 }

  • 事务(Transaction)
    事务就是将需要执行的方法使用 wrapper 封装起来,再通过事务提供的 perform 方法执行。 而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,执行完 perform 之后(即执行 method 方法后)再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper。
    而要使用事务的模 块,除了需要把 mixin 混入自己的事务实现中外,还要额外实现一个抽象的 getTransactionWrappers 接口。这个接口用来获取所有需要封装的前置方法(initialize)和收尾方法(close), 因此它需要返回一个数组的对象,每个对象分别有 key 为 initialize 和 close 的方法

    perform (func, scope,a,b,c,d,e,f){
      this.initializeAll(0);
      method.call(scope, a, b, c, d, e, f);
      this.closeAll(0);
    }
  • Diff算法

Diff算法本质上是对javascript对象之间的比较,只有在React更新阶段(调用了setState)才会有Diff算法的运用。
流程:

  • this.setState(partialState) 更新state
  • this.replaceState(completeState) 合并state
  • this._receivePropsAndState(this.props,nextState,transaction)收到新的props和state,决定是否更新组件

_receivePropsAndState: function(nextProps, nextState, transaction)

if (!this.shouldComponentUpdate ||  //没有定义shouldComponentUpdate函数
this.shouldComponentUpdate(nextProps, nextState)) {  //shouldComponentUpdate函数返回true
  this._performComponentUpdate(nextProps, nextState, transaction);
} else {  
//shouldComponentUpdate函数返回了false,不进行DOM更新,只更新props和state的值
  this.props = nextProps;
  this.state = nextState;
}

4.this._performComponentUpdate(nextProps, nextState, transaction);
调用this.componentWillUpdate
this.updateComponent(transaction);
调用this.componentDidUpdate

5.this.updateComponent(transaction)

updateComponent: function(transaction) {
var currentComponent = this._renderedComponent;  //原组件
var nextComponent = this._renderValidatedComponent();  //新组件
//两个组件的类相同(构造函数一样)
if (currentComponent.constructor === nextComponent.constructor) {
  if (!nextComponent.props.isStatic) {
    currentComponent.receiveProps(nextComponent.props, transaction); //更新组件
  }
} else {
  // 两个组件的类不一样,直接替换
  var thisID = this._rootNodeID;
  var currentComponentID = currentComponent._rootNodeID;
  //卸载原组件
  currentComponent.unmountComponent();
  //加载新组件
  var nextMarkup = nextComponent.mountComponent(thisID, transaction);
  ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
    currentComponentID,
    nextMarkup
  );
  this._renderedComponent = nextComponent;
}

}

  1. currentComponent.receiveProps(nextComponent.props, transaction)

有三种类型的component:
①文本 ReactTextComponent

receiveProps: function(nextProps, transaction) {
//text不一样直接替换
if (nextProps.text !== this.props.text) {
  this.props.text = nextProps.text;
  ReactComponent.DOMIDOperations.updateTextContentByID(
    this._rootNodeID,
    nextProps.text
  );
}
}

②React自定义组件
调用componentWillReceiveProps
再次调用this._receivePropsAndState(nextProps, nextState, transaction);

tree diff
比较两棵DOM树,如果某个节点不存在,则该节点及其子节点会被完全删除,不会进一步比较。 React只会简单地考虑同层级节点的位置变换
component diff
如果是同一类型组件,继续比较
如果不是,替换整个组件
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那 么就可以节省大量的 diff 运算时间。因此,React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff 算法分析。
element diff
1.比较新旧集合元素的key,如果有相同key,说明旧集合中有新集合的元素。
2.如果该元素在旧集合的index < lastIndex (lastindex指的是访问过的元素在旧集合中最大的index),移动该元素到nextIndex,否则不移动。
3.如果新集合里的元素在旧集合不存在,创建新元素到当前index。
4.更新lastIndex, nextIndex++

存在的缺陷
《深入React技术栈》作者观点是:
如果旧集合是A,B,C,D, 新集合是D,A,B,C
D不会移动,而ABC都要依次移动。
实际上D只要移动到C后面
本人认为:A,B,C的index都已经发生变化,所以肯定会有移动操作,避免不了。

相关推荐