美文网首页
《深入React技术栈》学习笔记Ⅲ

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

作者: 般犀 | 来源:发表于2018-07-31 09:32 被阅读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 版本新生命周期函数浅析及升级方案

    相关文章

      网友评论

          本文标题:《深入React技术栈》学习笔记Ⅲ

          本文链接:https://www.haomeiwen.com/subject/aorqvftx.html