react生命周期原理解析

6 篇文章 1 订阅
6 篇文章 1 订阅

这篇文章是对之前的一片文章react生命周期整理提到的生命周期的解析,本文会从源码的角度,对react的各个生命周期的实现原理进行不太详细的分析

getDefaultProps与getInitialState

首先我们来看一下以下两个代码片段,第一个是用es5创建组件的方法,需要调用react的createClasss方法,第二个是es6创建组件的方法,定义一个类继承react.component

var Greeting = React.createClass({
 
  getDefaultProps: function() {
    return {
      name: '小敏哥' //默认属性值
    };
  },
  
  getInitialState: function() {
    return {count: this.props.initialCount}; //初始化state
  },
  
  handleClick: function() {
    this.setState({count:this.state.count++})
  },

  render: function() {
    return <h1>Hello, {this.props.name},{this.state.count}</h1>;
  }
});
module.exports = Greeting;
class Greeting extends React.Component {

  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
    this.handleClick = this.handleClick.bind(this);
  }
 
  static defaultProps = {
     name: '小敏哥'
  };
  
  handleClick() {
    this.setState({count:this.state.count++})

  }
  
  render() {
    return <h1>Hello, {this.props.name},{this.state.count}</h1>;
  }
}


export default Greating;

上面的两段代码其实做的是同一件事:声明了一个自定义组件,贴出这两段代码,是为了从es5和es6的角度解释getDefaultProps和getInitialState这两个生命周期方法。

我们先看es6的方法,显然更优雅并且更容易理解,其实就是声明一个Component的子类,我们注意到,defaultProps是一个静态属性,而this.state在contructor中进行初始化,由于defaultProps是一个静态属性,所以它只会初始化一次,而state的初始化则在每次实例化的时候,都会通过constructor方法进行初始化,事实上,通过es6的写法,通过用静态defaultProps和在constructor方法中初始化state,已经不需要getDefaultProps和getInitialState这两个生命周期,但我们仍然需要知道,它做的是同一件事。

接着我们看es5的方法,它定义了getDefaultProps和getInitialState这两个生命周期方法,从这种写法看上去,其实我们看不出什么时候会执行这两个声明周期方法,这个时候,我们来看一下react的源码

createClass: function(spec) {
    var Constructor = function(props, context, updater) {
        //这里面是实例化时需要做的东西
      // This constructor gets overridden by mocks. The argument is used
      // by mocks to assert on what gets mounted.

      // Wire up auto-binding
      if (this.__reactAutoBindPairs.length) {
        bindAutoBindMethods(this);
      }

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

      this.state = null;

      // ReactClasses doesn't have constructors. Instead, they use the
      // getInitialState and componentWillMount methods for initialization.

      var initialState = this.getInitialState ? this.getInitialState() : null;

      invariant(
        typeof initialState === 'object' && !Array.isArray(initialState),
        '%s.getInitialState(): must return an object or null',
        Constructor.displayName || 'ReactCompositeComponent'
      );

      this.state = initialState;
    };
    Constructor.prototype = new ReactClassComponent();
    Constructor.prototype.constructor = Constructor;
    Constructor.prototype.__reactAutoBindPairs = [];

    injectedMixins.forEach(
      mixSpecIntoComponent.bind(null, Constructor)
    );

    mixSpecIntoComponent(Constructor, spec);

    // Initialize the defaultProps property after all mixins have been merged.
    if (Constructor.getDefaultProps) {
      Constructor.defaultProps = Constructor.getDefaultProps();
    }

    invariant(
      Constructor.prototype.render,
      'createClass(...): Class specification must implement a `render` method.'
    );

    // Reduce time spent doing lookups by setting these on the prototype.
    for (var methodName in ReactClassInterface) {
      if (!Constructor.prototype[methodName]) {
        Constructor.prototype[methodName] = null;
      }
    }

    return Constructor;
  },

为了理解方便,我删除了一小部分不影响理解的代码,我们可以注意到这个createClass就是一个构造函数,它返回了一个Constructor,实例化组件的时候,我们就是去new这个Constructor来生成组件,这里我们注意到这句代码

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

我们可以发现,这里做的,就是es6中静态属性defaultProps做的事,只不过通过getDefaultProps这个方法把数据给传进来,并且将其执行结果赋值给Constructor,这样一来,getDefaultProps这个方法只会执行一次在es5的写法中也就完全解释得通了,因为无论你如何去new createClass返回的方法去生成对象,这个方法已经在构造函数里面执行了,而初始state则通过一下两句代码进行初始化

 var initialState = this.getInitialState ? this.getInitialState() : null;
 this.state = initialState;

但我们可以看到,这两句代码是在Constructor中执行的,也就是说实在实例化过程中才会执行getInitialState,每次实例化的时候,getInitialState返回的值,将会作为组件的默认state

这样一来,通过对代码的分析,我们对这两个生命周期的理解也就大体清晰了,getDefaultProps(或者说默认props初始化,es6写法不存在这个方法)只会执行一次,而getInitialState(或者说默认state初始化,es6写法也不存在这个方法)则会在每次实例化的时候执行。(此处放个小括号并且加斜体吐槽一下《深入react技术栈》中的表述,里面是这样写的:当使用 ES6 classes 编写 React 组件时,class MyComponent extends React.Component 其实就是调用内部方法 createClass 创建组件。经过上面的分析之后,发现这不是扯淡么,两种不同的写法,es6写法根本就不会去调用createClass方法,如有不同意见可在评论区留言指教,谢谢)

componentWillMount,render和componentDidMount

将这三个放到一起写的原因在于,因为他们都是在第一次渲染的时候执行(主要还是因为源码离得近),当组件首次渲染时,会调用mountComponent方法,源码如下所示,其中,主要的渲染操作是在performInitialMount中进行的

mountComponent: function (
    transaction,
    nativeParent,
    nativeContainerInfo,
    context
) {
    this._context = context;
    this._mountOrder = nextMountID++;

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

    var Component = this._currentElement.type;

    // Initialize the public class
    var inst;
    var renderedElement;

    //创建组件
    if (Component.prototype && Component.prototype.isReactComponent) {
        inst = new Component(publicProps, publicContext, ReactUpdateQueue);
    } else {
        inst = Component(publicProps, publicContext, ReactUpdateQueue);
        if (inst == null || inst.render == null) {
            renderedElement = inst;
            warnIfInvalidElement(Component, renderedElement);
            inst = new StatelessComponent(Component);
        }
    }


    // These should be set up in the constructor, but as a convenience for
    // simpler class abstractions, we set them up after the fact.
    inst.props = publicProps;
    inst.context = publicContext;
    inst.refs = emptyObject;
    inst.updater = ReactUpdateQueue;

    this._instance = inst;

    // Store a reference from the instance back to the internal representation
    ReactInstanceMap.set(inst, this);


    var initialState = inst.state;
    if (initialState === undefined) {
        inst.state = initialState = null;
    }

    //开始执行渲染操作
    var markup;
    
    markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context);

    //渲染完成,执行componentDidMount
    if (inst.componentDidMount) {
        transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
    }

    return markup;
}


performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) {
    var inst = this._instance;
    //判断是否有componentWillMount,有则执行
    if (inst.componentWillMount) {
        inst.componentWillMount();
        // When mounting, calls to `setState` by `componentWillMount` will set
        // `this._pendingStateQueue` without triggering a re-render.
        if (this._pendingStateQueue) {
            inst.state = this._processPendingState(inst.props, inst.context);
        }
    }

    // If not a stateless component, we now render
    if (renderedElement === undefined) {
        renderedElement = this._renderValidatedComponent();
    }

    this._renderedNodeType = ReactNodeTypes.getType(renderedElement);
    this._renderedComponent = this._instantiateReactComponent(
        renderedElement
    );

    //开始递归渲染
    var markup = ReactReconciler.mountComponent(
        this._renderedComponent,
        transaction,
        nativeParent,
        nativeContainerInfo,
        this._processChildContext(context)
    );

    return markup;
},

长长的一大段代码,同样为了便于理解,删除了一部分代码,其实也就是想说明一点,componentWillMount定义之后会在渲染之前被调用,componentWillMount被调用之后,会进行state的合并操作,而componentDidMount则会在渲染完成之后被调用,render函数则会在渲染的过程中被调用。此处需要特别注意一行代码:

if (this._pendingStateQueue) {
    inst.state = this._processPendingState(inst.props, inst.context);
}

这两行代码的作用是用于合并当前state,如果我们在componentWillMount中执行setState方法的话,是不会触发react的重新渲染的,只会将当前的state合并到缓存中,等到整个渲染事务完结之后,react才会对整个state进行重新进行渲染,这主要是基于性能的考虑,毕竟如果每次setState都重新渲染,将会非常影响性能,所以react对于setState有一套基于事务的操作,但是由于setState基于事务的渲染机制比较复杂,可以重新开一篇文章进行解析了,此处就不再赘述,这里只需要知道,这行代码是用于合并state,并且它在componentWillMount之后执行,所以我们在componentWillMount中获取到的state,并不是最新的,而且即使在componentWillMount中进行setState,也不会触发重新渲染,如果要获取到最新的state,则需等到生命周期到达render或者componentDidMount中,获取的state才会是准确的。

componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,componentDidUpdate

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

    // 检查context是否改变
    if (this._context === nextUnmaskedContext) {
        nextContext = inst.context;
    } else {
        nextContext = this._processContext(nextUnmaskedContext);
        willReceive = true;
    }

    //检测props是否发生变化
    // Distinguish between a props update versus a simple state update
    if (prevParentElement === nextParentElement) {
        // Skip checking prop types again -- we don't read inst.props to avoid
        // warning for DOM component props in this upgrade
        nextProps = nextParentElement.props;
    } else {
        nextProps = this._processProps(nextParentElement.props);
        willReceive = true;
    }

    if (willReceive && inst.componentWillReceiveProps) {
        inst.componentWillReceiveProps(nextProps, nextContext);
    }

    //合并state,但仅获得新的state,并未赋值
    var nextState = this._processPendingState(nextProps, nextContext);

    //判断当前是否允许更新
    var shouldUpdate =
        this._pendingForceUpdate ||
        !inst.shouldComponentUpdate ||
        inst.shouldComponentUpdate(nextProps, nextState, nextContext);

    if (shouldUpdate) {
        //允许更新,则进入下一步
        this._pendingForceUpdate = false;
        // Will set `this.props`, `this.state` and `this.context`.
        this._performComponentUpdate(
            nextParentElement,
            nextProps,
            nextState,
            nextContext,
            transaction,
            nextUnmaskedContext
        );
    } else {
        //不允许更新,数据保留
        // If it's determined that a component should not update, we still want
        // to set props and state but we shortcut the rest of the update.
        this._currentElement = nextParentElement;
        this._context = nextUnmaskedContext;
        inst.props = nextProps;
        inst.state = nextState;
        inst.context = nextContext;
    }
},



_performComponentUpdate: function(
    nextElement,
    nextProps,
    nextState,
    nextContext,
    transaction,
    unmaskedContext
) {
    var inst = this._instance;

    var hasComponentDidUpdate = Boolean(inst.componentDidUpdate);
    var prevProps;
    var prevState;
    var prevContext;
    if (hasComponentDidUpdate) {
        prevProps = inst.props;
        prevState = inst.state;
        prevContext = inst.context;
    }
    //执行钩子函数
    if (inst.componentWillUpdate) {
        inst.componentWillUpdate(nextProps, nextState, nextContext);
    }

    this._currentElement = nextElement;
    this._context = unmaskedContext;
    inst.props = nextProps;
    inst.state = nextState;
    inst.context = nextContext;

    //渲染组件
    this._updateRenderedComponent(transaction, unmaskedContext);

    //渲染完成后调用componentDidUpdate
    if (hasComponentDidUpdate) {
        transaction.getReactMountReady().enqueue(
            inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext),
            inst
        );
    }
},

又是洋洋洒洒一百多行源码,大概写清楚当props或者state发生变化时,这几个钩子函数时怎么执行的,首先通过标记willReceive变量,当context或者props发生变化时,如果存在componentWillReceiveProps,则执componentWillReceiveProps,如果仅仅只是state发生变化,则会跳过,接着执行state合并,然后会执行shouldComponentUpdate,获得该函数的返回值,用于判断渲染是否需要往下进行,如果不往下进行,则仅保留数据,并不进行渲染,如果进行渲染,则会执行_performComponentUpdate方法进行具体的渲染,之后执行componentWillUpdate方法,这是在渲染之前的最后一个钩子函数,然后我们注意到执行componentWillUpdate之后,下面的这句代码

inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;

在这之后才出现props,state,context的更新,那么说明,在这之前,我们在componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate这几个钩子函数中获取到的props,state,context都将不会是最新的,同样,由于setState的事务尚未完成,在componentWillReceiveProps中进行setState操作仅仅会合并当前state并在事务完成后一起进行渲染。最后,当所有的渲染操作完成之后,如果定义了componentDidUpdate,则会调用componentDidUpdate函数

至于componentWillUnmount,大体上就是在组件卸载时会被调用,具体将会unmountComponent函数,因为实现比较简单,此处就不再贴出来了,有兴趣的读者可以自行查看源码,毕竟,源码贴的有点多了(源码比文字多系列)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值