React生命周期浅谈

  React学习过程中,对于组件最重要的(也可能是之一)的莫过于是组件的生命周期了,React相当于使用状态来映射到界面输出,通过状态的改变从而改变界面效果。在状态的改变过程中,必须要经历组件的生命周期。

  React会经历三个阶段:mount、update、unmount,每个阶段对应两个生命周期(ummount除外):Will(对应进入)与Did(对应结束),因而存在五个对应的方法,并且在update阶段存在两种特殊的方法:shouldComponentUpdatecomponentWillReceiveProps,这
些函数基本构成了React的生命周期。如下图所示:

  上图中的getDefaultPropsgetInitialState分别对应ES6中的static defaultProps = {}与构造函数construct中的this.state ={}赋值。下面我们按照上图的过程依次介绍:(介绍主要以React.createClass为例,基本等同于extends React.Component)

React生命周期

初次渲染

//本文代码基于15.0,只删选其中有用的部分,注释来源于《深入React技术栈》
var React = {
  //...
  createClass: ReactClass.createClass
  //...
};

var ReactClass = {
  // 创建自定义组件
  createClass: function(spec) {
    var Constructor = function(props,context,updater) {
      // 自动绑定
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

      this.props = props;
      this.context = context;
      this.refs = emptyObject;
      this.updater = updater || ReactNoopUpdateQueue;
      this.state = null;

      // ReactClass 没有构造函数,通过 getInitialState 和 componentWillMount 来代替
      var initialState = this.getInitialState ? this.getInitialState() : null;
      this.state = initialState;
    };

    // 原型继承父类
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

    // 合并 mixin
    injectedMixins.forEach(
      mixSpecIntoComponent.bind(null,Constructor)
    );

    mixSpecIntoComponent(Constructor,spec);

    // 所有 mixin 合并后初始化 defaultProps(在整个生命周期中,getDefaultProps 只执行一次)
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }
    // 减少查找并设置原型的时间
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }
    return Constructor;
  },};

  总结一下上面的代码React.createClass返回函数Constructor(props,updater)用来生成组件的实例,而React.createClass执行的时候会执行包括:合并mixin,获取默认属性defaultProps将其赋值到Constructor的原型中,并且也将传入React.createClass中的方法赋值到Constructor的原型中,以缩短再次查找方法的时间。
  在这个函数中,我们关心的部分其实主要集中在:

if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
}

  我们发现在调用React.createClass,已经执行了getDefaultProps(),并将其赋值于Constructor的原型中,所以我们对照声明周期图可以得到:

React中的getDefaultProps()仅会在整个生命周期中只执行一次,并且初始化的实例都会共享该defaultProps

  ReactCompositeComponent中的mountComponentupdateComponentunmountComponent分别对应于React中mount、update、unmount阶段的处理,首先大致看一下mount阶段的简要代码:

// 当组件挂载时,会分配一个递增编号,表示执行 ReactUpdates 时更新组件的顺序
var nextMountID = 1;

var ReactCompositeComponent = {
    /**
     * 组件初始化,渲染、注册事件
     * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
     * @param {?object} hostParent
     * @param {?object} hostContainerInfo
     * @param {?object} context
     * @return {?string} 返回的markup会被插入DOM中.
     * @final
     * @internal
     */
    mountComponent: function (transaction,nativeParent,nativeContainerInfo,context) {
        // 当前元素对应的上下文
        this._context = context;
        this._mountOrder = nextMountID++;
        this._nativeParent = nativeParent;
        this._nativeContainerInfo = nativeContainerInfo;

        var publicProps = this._processProps(this._currentElement.props);
        var publicContext = this._processContext(context);

        var Component = this._currentElement.type;

        // 初始化公共类
        var inst = this._constructComponent(publicProps,publicContext);
        var renderedElement;

        // 用于判断组件是否为 stateless,无状态组件没有状态更新队列,它只专注于渲染
        if (!shouldConstruct(Component) && (inst == null || inst.render == null)) {
            renderedElement = inst;
            warnIfInvalidElement(Component,renderedElement);
            inst = new StatelessComponent(Component);
        }

        // 这些初始化参数本应该在构造函数中设置,在此设置是为了便于进行简单的类抽象
        inst.props = publicProps;
        inst.context = publicContext;
        inst.refs = emptyObject;
        inst.updater = ReactUpdateQueue;

        this._instance = inst;

        // 将实例存储为一个引用
        ReactInstanceMap.set(inst,this);

        // 初始化 state
        var initialState = inst.state;
        if (initialState === undefined) {
            inst.state = initialState = null;
        }

        // 初始化更新队列
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;

        var markup;
        // 如果挂载时出现错误
        if (inst.unstable_handleError) {
            markup = this.performInitialMountWithErrorHandling(renderedElement,transaction,context);
        } else {
            // 执行初始化挂载
            markup = this.performInitialMount(renderedElement,context);
        }

        // 如果存在 componentDidMount,则调用
        if (inst.componentDidMount) {
            transaction.getReactMountReady().enqueue(inst.componentDidMount,inst);
        }

        return markup;
    },performInitialMountWithErrorHandling: function (renderedElement,context) {
        var markup;
        var checkpoint = transaction.checkpoint();

        try {
            // 捕捉错误,如果没有错误,则初始化挂载
            markup = this.performInitialMount(renderedElement,context);
        } catch (e) {
            transaction.rollback(checkpoint);
            this._instance.unstable_handleError(e);
            if (this._pendingStateQueue) {
                this._instance.state = this._processPendingState(this._instance.props,this._instance.context);
            }
            checkpoint = transaction.checkpoint();

            // 如果捕捉到错误,则执行 unmountComponent 后,再初始化挂载
            this._renderedComponent.unmountComponent(true);
            transaction.rollback(checkpoint);

            markup = this.performInitialMount(renderedElement,context);
        }
        return markup;
    },performInitialMount: function (renderedElement,context) {
        var inst = this._instance;
        // 如果存在 componentWillMount,则调用
        if (inst.componentWillMount) {
            inst.componentWillMount();
            // componentWillMount 调用 setState 时,不会触发 re-render 而是自动提前合并
            if (this._pendingStateQueue) {
                inst.state = this._processPendingState(inst.props,inst.context);
            }
        }

        // 如果不是无状态组件,即可开始渲染
        if (renderedElement === undefined) {
            renderedElement = this._renderValidatedComponent();
        }

        this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
        // 得到 _currentElement 对应的 component 类实例
        this._renderedComponent = this._instantiateReactComponent(
            renderedElement
        );
        // render 递归渲染
        var markup = ReactReconciler.mountComponent(this._renderedComponent,this._processChildContext(context));

        return markup;
    }
}

  我们现在只关心生命周期相关的代码,初始化及其他的代码暂时不考虑,我们发现初始化state之后会进入渲染的步骤,根据是否存在错误,选择性执行performInitialMountWithErrorHandlingperformInitialMount,我们仅考虑正常情况下的performInitialMount

// 如果存在 componentWillMount,则调用
 if (inst.componentWillMount) {
     inst.componentWillMount();
     // componentWillMount 调用 setState 时,不会触发 re-render 而是自动提前合并
     if (this._pendingStateQueue) {
         inst.state = this._processPendingState(inst.props,inst.context);
     }
 }

  如果存在componentWillMount则执行,如果在componentWillMount执行了setState方法,在componentWillMount并不会得到已经更新的state,因为我们发现的state的合并过程是在componentWillMount结束后才执行的。然后在performInitialMount(为例)会进行递归渲染,
然后在递归执行结束后,返回markup(返回的markup会被插入DOM中)。然后,如果存在 componentDidMount。并且由于渲染的过程都是递归的,我们可以综合得到渲染阶段的生命周期(包括子节点)如下:

更新阶段

  首先还是看一下简要的更新阶段的代码:

var ReactCompositeComponent = {
    /**
     * 更新已经渲染的组件。componentWillReceiveProps和shouldComponentUpdate方法将会被调用
     * 然后,(更新的过程没有被省略),其余的更新阶段的生命周期都会被调用,对应的DOM会被更新。
     * @param {ReactReconcileTransaction} transaction
     * @param {ReactElement} prevParentElement
     * @param {ReactElement} nextParentElement
     * @internal
     * @overridable
     */

    updateComponent: function (transaction,prevParentElement,nextParentElement,prevUnmaskedContext,nextUnmaskedContext) {
        var inst = this._instance;
        var willReceive = false;
        var nextContext;
        var nextProps;

        // Determine if the context has changed or not
        if (this._context === nextUnmaskedContext) {
            nextContext = inst.context;
        } else {
            nextContext = this._processContext(nextUnmaskedContext);
            willReceive = true;
        }

        var prevProps = prevParentElement.props;
        var nextProps = nextParentElement.props;

        // Not a simple state update but a props update
        if (prevParentElement !== nextParentElement) {
            willReceive = true;
        }

        // 如果存在 componentWillReceiveProps,则调用
        if (willReceive && inst.componentWillReceiveProps) {
            inst.componentWillReceiveProps(nextProps,nextContext);
        }

        // 将新的 state 合并到更新队列中,此时 nextState 为最新的 state
        var nextState = this._processPendingState(nextProps,nextContext);

        // 根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件
        var shouldUpdate =
            this._pendingForceUpdate || !inst.shouldComponentUpdate ||
            inst.shouldComponentUpdate(nextProps,nextState,nextContext);

        if (shouldUpdate) {
            // 重置更新队列
            this._pendingForceUpdate = false;
            // 即将更新 this.props、this.state 和 this.context
            this._performComponentUpdate(nextParentElement,nextProps,nextContext,nextUnmaskedContext);
        } else {
            // 如果确定组件不更新,仍然要设置 props 和 state
            this._currentElement = nextParentElement;
            this._context = nextUnmaskedContext;
            inst.props = nextProps;
            inst.state = nextState;
            inst.context = nextContext;
        }
    },//当确定组件需要更新时,则调用
    _performComponentUpdate: function (nextElement,unmaskedContext) {
        var inst = this._instance;
        var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
        var prevProps;
        var prevState;
        var prevContext;

        // 如果存在 componentDidUpdate,则将当前的 props、state 和 context 保存一份
        if (hasComponentDidUpdate) {
            prevProps = inst.props;
            prevState = inst.state;
            prevContext = inst.context;
        }

        // 如果存在 componentWillUpdate,则调用
        if (inst.componentWillUpdate) {
            inst.componentWillUpdate(nextProps,nextContext);
        }

        this._currentElement = nextElement;
        this._context = unmaskedContext;

        // 更新 this.props、this.state 和 this.context
        inst.props = nextProps;
        inst.state = nextState;
        inst.context = nextContext;

        // 实现代码省略,递归调用 render 渲染组件
        this._updateRenderedComponent(transaction,unmaskedContext);

        // 当组件完成更新后,如果存在 componentDidUpdate,则调用
        if (hasComponentDidUpdate) {
            transaction.getReactMountReady().enqueue(
                inst.componentDidUpdate.bind(inst,prevProps,prevState,prevContext),inst
            );
        }
    }
}

  判断更新阶段是否需要调用componentWillReceiveProps主要通过如下,同样我们只关心生命周期相关的代码,其他的代码暂时不考虑:

if (this._context === nextUnmaskedContext) {
    nextContext = inst.context;
} else {
    nextContext = this._processContext(nextUnmaskedContext);
    willReceive = true;
}

var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;

// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
    willReceive = true;
}

// 如果存在 componentWillReceiveProps,则调用
if (willReceive && inst.componentWillReceiveProps) {
    inst.componentWillReceiveProps(nextProps,nextContext);
}

  所以我们可以分析得出只有在context发生变化或者parentElement前后不一致(prevParentElement !== nextParentElement)时,willReceive才为true,这时,如果存在componentWillReceiveProps,就会被调用。那么我们需要了解的是parentElement存储的是什么信息,parentElement存储的信息如下:

{
    $$typeof:Symbol(react.element),key:null,props:Object,ref:null,type: function Example(props),_owner:ReactCompositeComponentWrapper,_store:Object,_self:App,_source:Object,__proto__:Object
}

  我们发现,parentElement是不含父组件的state信息的。因此我们还可以得到下面的结论: 如果父组件的props等信息发生改变时,即使这个改变的属性没有传入到子组件,但也会引起子组件的componentWillReceiveProps的执行
并且我们可以发现,如果在componentWillReceiveProps中调用setState,state是不会立即得到更新。state会在componentWillReceiveProps后合并,所以componentWillReceiveProps中是不能拿到新的state

需要注意的是

不能在 shouldComponentUpdatecomponentWillUpdate 中调用 setState,原因是shouldComponentUpdatecomponentWillUpdate调用setState会导致再次调用updateComponent,这会造成循环调用,直至耗光浏览器内存后崩溃。

var shouldUpdate =
    this._pendingForceUpdate || !inst.shouldComponentUpdate ||
    inst.shouldComponentUpdate(nextProps,nextContext);

if (shouldUpdate) {
    // 重置更新队列
    this._pendingForceUpdate = false;
    // 即将更新 this.props、this.state 和 this.context
    this._performComponentUpdate(nextParentElement,nextUnmaskedContext);
} else {
    // 如果确定组件不更新,仍然要设置 props 和 state
    this._currentElement = nextParentElement;
    this._context = nextUnmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;
}

  然后我们会根据shouldComponentUpdate返回的内容,决定是否执行全部的声明周期更新操作。如果返回false,就不会执行接下来的更新操作。但是,从上面看得出,即使shouldComponentUpdate返回了false,组件中的propsstate以及state的都会被更新(当然,调用了forceUpdate函数的话,会跳过shouldComponentUpdate的判断过程。)
如果shouldComponentUpdate返回true或者没有定义shouldComponentUpdate函数,就会进行进行组件更新。如果存在componentDidUpdate,会将更新前的statepropscontext保留一份备份。如果存在componentWillUpdate,则调用。接着递归调用render进行渲染更新。当组件完成更新后,如果存在componentDidUpdate函数就会被调用,
并将更新前的状态备份和当前的状态作为参数传递。

卸载阶段

var ReactCompositeComponent = {
    /**
     * 释放由`mountComponent`分配的资源.
     *
     * @final
     * @internal
     */
    unmountComponent: function(safely) {
        if (!this._renderedComponent) {
            return;
        }
        var inst = this._instance;
        // 如果存在 componentWillUnmount,则调用
        if (inst.componentWillUnmount) {
            if (safely) {
                var name = this.getName() + '.componentWillUnmount()';
                ReactErrorUtils.invokeGuardedCallback(name,inst.componentWillUnmount.bind(inst));
            } else {
                inst.componentWillUnmount();
            }
        }
        // 如果组件已经渲染,则对组件进行 unmountComponent 操作
        if (this._renderedComponent) {
            ReactReconciler.unmountComponent(this._renderedComponent,safely);
            this._renderedNodeType = null;
            this._renderedComponent = null;
            this._instance = null;
        }
        // 重置相关参数、更新队列以及更新状态
        this._pendingStateQueue = null;
        this._pendingReplaceState = false;
        this._pendingForceUpdate = false;
        this._pendingCallbacks = null;
        this._pendingElement = null;
        this._context = null;
        this._rootNodeID = null;
        this._topLevelWrapper = null;
        // 清除公共类
        ReactInstanceMap.remove(inst);
    }
}

  卸载阶段非常简单,如果存在componentWillUnmount函数,则会在更新前调用。然后递归调用清理渲染。最后将相关参数、更新队列以及更新状态进行重置为空。
  本来想接着写一下setState和React Transaction,发现太弱鸡了,并没有看懂,现在正在学习研究中,大家以后可以关注一下~

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom