《深入React技术栈》学习笔记Ⅲ

2018-07-31  本文已影响0人  般犀

以下的生命周期都是在 React 15 的生命周期, React 16 的生命周期 API 已经发生变化。React 16 加入了 getDerivedStateFromProps(直译:从 props 中拿到派生 state)和getSnapshotBeforeUpdate(直译:在更新之前拿到快照),现在生命周期中的 ComponentWillMount.componentWillReceivedPropsComponentWillUpdate将在 React 17 中废除。
3.3 生命周期的管理艺术
React 组件的生命周期就是一个有限状态机运动的过程。

3.3.1 初探 React 生命周期
组件的生命周期在不同状态下又不同的执行顺序:


React 生命周期的5种情况

在使用 ES6 Class 语法创建组件的时候,static defaultProps = {}实际上就是在走生命周期的getDefaultPropsthis.setState = {}就是生命周期的 getInitialState

3.3.2 详解 React 生命周期


MOUNTING

细节讲解
最外层的元素实际上不是 <App/>,而是在html中的那个真实 DOM节点 <div id="app"></app>,被称为 TopLevelWrapper,是树的根节点,被赋予的节点ID是1,从他开始,依次标记每个 组件。且只有组件会被标记ID,普通dom标记不会被标上id(尽管在vitrutal dom里普通标签也是 reactElement 类型)
每一个被mounting的组件都会执行构造函数 ReactCompositeComponentMixin(element),每个组件都是由这个构造函数实例出来的对象的一个属性(this._currentElement = element),这个构造函数实例出来的对象包括以下属性:

this._currentElement: 当前元素
this._rootNodeID: 每个组件都会被编号,从 1开始
this._nativeParent: 组件的父组件
... 

与更新有关的
this._pendingElement = null;
this._pendingStateQueue = null;
this._pendingReplaceState = false;
this._pendingForceUpdate = false;

this._renderedNodeType = null;
this._renderedComponent = null;
this._context = null;
this._mountOrder = 0;
this._topLevelWrapper = null;

// See ReactUpdates and ReactUpdateQueue.
this._pendingCallbacks = null;

阶段一: MOUNTING
mountComponent 负责管理生命周期中的 getInitialStatecomponentWillMountrendercomponentDidMount,而getDefaultProps实际上是构造函数管理的,所以这就是为什么第二次挂载组件的时候getDefaultProps不会触发。

首先,通过 mountComponent 挂载组件,初始化序号,标记等参数,判断是否是无状态组件,并进行对应的组件初始化工作,如初始化 props,context等参数。利用 getInitialState 获取初始化 state,初始化更新队列和更新状态。
若存在 componentWillMount,则执行,componentWillMount中调用 setState()是不会触发 re-render 的,而是会进行 state 合并。且这个合并(inst.state = this._processPendingState(init.props, inst.context))是在componentWillMount之后才执行。,所以 componentWillMount中的 this.state 并不是最新的。
因此,React 更新 state 的过程就是:利用更新队列 this._penndingStateQueue 以及更新状态this._pendingReplaceStatethis._pendingForceUpdate来实现 state 的异步更新队列。
mountComponent本质上是通过**递归**渲染的。所以父组件的componentWillMount在子组件的componentWillMount之前调用,componentDidMount在子组件的componentDidMount```之后调用。(合情合理,只有子组件都挂在好了,父组件才算挂载好了)。

实际上,无状态组件也会执行 mountComponent,只是他们没有生命周期而已

阶段二 RECEIVE_PROPS
updateComponent 负责生命周期中的 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

RECEIVE_PROPS

componentWillReceiveProps只有在组件的父组件更新时才会调用,

阶段三 UNMOUNTING
unmountComponent 负责管理生命周期中的 componentWillUnmount

UNMOUNTING

在这个阶段 setState()是无效的,因为组件的属性(_renderedNodeType,_renderedComponent,_instance,_pendingStateQueue,_pendingReplaceState,_pendingForceUpdate,_pendingCallbacks,_pendingElement,_context,_rootNodeID,_topLevelWrapper)都会被置 null,这些是更新队列,更新状态,公共类等(搞清楚这些属性的作用)。

3.3.3 无状态组件
无状态组件的构造函数上就只有一个 render方法。

无状态组件的构造函数

3.4 解密 setState 机制
setState 会通过一个更新队列实现 state 更新,将需要更新的 state 合并后放入状态队列。如果直接用 this.state.value=1的方式修改state,很有可能在更新队列中被其他值覆盖。React 使用 了更新队列合并多次 state 的修改,避免多次更新。
setState中会调用 _processPendingState,用于状态合并

// performUpdateIfNecessary 之后调用
var nextState = this._processPendingState(nextProps, nextContext);
// _processPendingState执行 state 的合并
_processPendingState: function (props, context) {
    var inst = this._instance;
    // queue 就是更新队列,一个数组
    var queue = this._pendingStateQueue;
    var replace = this._pendingReplaceState;
    // 把 _pendingReplaceState和_pendingStateQueue 赋给 queue 和 replace 后马上置空
    this._pendingReplaceState = false;
    this._pendingStateQueue = null;
    // 如果更新队列无数据,直接返回原来的state
    if (!queue) {
      return inst.state;
    }

    if (replace && queue.length === 1) {
      return queue[0];
    }
   
    var nextState = _assign({}, replace ? queue[0] : inst.state);
    for (var i = replace ? 1 : 0; i < queue.length; i++) {
      var partial = queue[i];
     // partial 可能是函数,也可能是对象,看你怎么调的 setState
      _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
    }
    // nextState 里是新的 state, eg: {a: 1, b: 0}
    return nextState;
  }

setState 实际上会执行 enqueueSetState的方法,将partialState_pendingStateQueue合并,并用 enqueueSetState执行 state 更新。

更新 state 还有一个方法是 enqueueForceUpdate,它可以不调用 setState就更新 state,应该是老手使用的。还有一个 enqueueReplaceState是直接替换 state,但使用它就无法保证 state 的不可变性。而且不会立即更新,所以在调用enqueueReplaceState后再调用 this.state有可能拿到的是旧的值。

// 都是调用 warnTDZ,但第二个参数不同
enqueueForceUpdate: function (publicInstance) {
    warnTDZ(publicInstance, 'forceUpdate');
  }
enqueueReplaceState: function (publicInstance, completeState) {
    warnTDZ(publicInstance, 'replaceState');
  }
enqueueSetState: function (publicInstance, partialState) {
    warnTDZ(publicInstance, 'setState');
  }

但是很奇怪warnTDZ只是用来在development模式下返回一个警告,你调用了一个没有挂载的组件???

3.4.2 setState 循环调用的风险
只要存在 _pendingElement(待更新Element),_pendingStateQueue(待更新队列),_pendingForceUpdate(待强迫更新队列),就会执行 performIfNecessary,调用方式如下:

performUpdateIfNecessary 的两种调用情况

如果在shouldComponetUpdatecomponentWillUpdate中调用 setState,会让 _pendingStateQueue不为 nullperformUpdateIfNecessary就会执行updateComponent,而updateComponent又会调用shouldComponetUpdatecomponentWillUpdate,从而导致 循环调用(试了下真的会,最后导致栈溢出

彩蛋: 自己发现的 setState 现象

  1. setState不被允许传入 null,传入 null会报错,提醒你可以用 forceUpdate
ReactComponent.prototype.setState = function (partialState, callback) {
  // 对 partialState 进行校验,必须是 object ,function ,最次是 null
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) 
  ? process.env.NODE_ENV !== 'production' 
      ? invariant(false, 'setState(...): takes an object of state variables to update or a ' + 'function which returns an object of state variables.')
      : invariant(false) 
  : void 0;
 // 传 null 会有警告,建议你用 forceUpdate
  if (process.env.NODE_ENV !== 'production') {
    ReactInstrumentation.debugTool.onSetState();
    process.env.NODE_ENV !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : void 0;
  }
// 进入 enqueueSetState, 传入的 this 是 组件本身
  this.updater.enqueueSetState(this, partialState);
// 执行 回调
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

3.4.3 setState 调用栈
可以看到最终 setStateenqueueSetState执行 state 更新,下面将介绍 enqueueSetState将如何更新

容易混淆的:
_pendingProcessQueue_processPendingState

_pendingProcessQueue是更新队列,一个数组,_processPendingState是一个方法,更新队列在_processPendingState中被遍历,最后_processPendingState返回 nextState是合并了更新数据后的最新 state(包括没有被变动的 state)。

参考文章:
React v16.3 版本新生命周期函数浅析及升级方案

上一篇下一篇

猜你喜欢

热点阅读