React源码分析4 — React生命周期详解
来源:互联网 发布:大数据在传媒界的应用 编辑:程序博客网 时间:2024/05/01 00:37
1 React生命周期流程
调用流程可以参看上图。分为实例化,存在期和销毁三个不同阶段。介绍生命周期流程的文章很多,相信大部分同学也有所了解,我们就不详细分析了。很多同学肯定有疑问,这些方法在React内部是在哪些方法中被调用的呢,他们触发的时机又是什么时候呢。下面我们来详细分析。
2 实例化生命周期
getDefaultProps
在React.creatClass()初始化组件类时,会调用getDefaultProps(),将返回的默认属性挂载到defaultProps变量下。这段代码之前已经分析过了,参考 React源码分析2 — 组件和对象的创建(createClass,createElement).
这里要提的一点是,初始化组件类只运行一次。可以把它简单类比为Java中的Class对象。初始化组件类就是ClassLoader加载Class对象的过程。类对象的初始化不要错误理解成了实例对象的初始化。一个React组件类可能会在JSX中被多次调用,产生多个组件对象,但它只有一个类对象,也就是类加载后getDefaultProps就不会再调用了。
getInitialState
这个方法在创建组件实例对象的时候被调用,具体代码位于React.creatClass()的Constructor函数中。之前文章中已经分析了,参考 React源码分析2 — 组件和对象的创建(createClass,createElement)。
每次创建React实例对象时,它都会被调用。
mountComponent
componentWillMount,render,componentDidMount都是在mountComponent中被调用。在React源码分析3 — React组件插入DOM流程一文中,我们讲过mountComponent被调用的时机。它是在渲染新的ReactComponent中被调用的。输入ReactComponent,返回组件对应的HTML。把这个HTML插入到DOM中,就可以生成组件对应的DOM对象了。所以mountComponent尤其关键。
和Java中的多态一样,不同的React组件的mountComponent实现都有所区别。下面我们来重点分析React自定义组件类,也就是ReactCompositeComponent的mountComponent。
mountComponent: function (transaction, nativeParent, nativeContainerInfo, context) { this._context = context; this._mountOrder = nextMountID++; this._nativeParent = nativeParent; this._nativeContainerInfo = nativeContainerInfo; // 做propTypes是否合法的判断,这个只在开发阶段有用 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; // inst或者inst.render为空对应的是stateless组件,也就是无状态组件 // 无状态组件没有实例对象,它本质上只是一个返回JSX的函数而已。是一种轻量级的React组件 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; // 存储实例对象的引用到map中,方便以后查找 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) { // 挂载时出错,进行一些错误处理,然后performInitialMount,初始化挂载 markup = this.performInitialMountWithErrorHandling(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } else { // 初始化挂载 markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } if (inst.componentDidMount) { // 调用componentDidMount,以事务的形式 transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); } return markup; },
mountComponent先做实例对象的props,state等初始化,然后调用performInitialMount初始化挂载,完成后调用componentDidMount。这个调用链还是很清晰的。下面我们重点来分析performInitialMountWithErrorHandling和performInitialMount
performInitialMountWithErrorHandling: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) { var markup; var checkpoint = transaction.checkpoint(); try { // 放到try-catch中,如果没有出错则调用performInitialMount初始化挂载。可见这里没有什么特别的操作,也就是做一些错误处理而已 markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } catch (e) { // handleError,卸载组件,然后重新performInitialMount初始化挂载 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(); this._renderedComponent.unmountComponent(true); transaction.rollback(checkpoint); markup = this.performInitialMount(renderedElement, nativeParent, nativeContainerInfo, transaction, context); } return markup; },
可见performInitialMountWithErrorHandling只是多了一层错误处理而已,关键还是在performInitialMount中。
performInitialMount: function (renderedElement, nativeParent, nativeContainerInfo, transaction, context) { var inst = this._instance; if (inst.componentWillMount) { // render前调用componentWillMount inst.componentWillMount(); // 将state提前合并,故在componentWillMount中调用setState不会触发重新render,而是做一次state合并。这样做的目的是减少不必要的重新渲染 if (this._pendingStateQueue) { inst.state = this._processPendingState(inst.props, inst.context); } } // 如果不是stateless,即无状态组件,则调用render,返回ReactElement if (renderedElement === undefined) { renderedElement = this._renderValidatedComponent(); } // 得到组件类型,如空组件ReactNodeTypes.EMPTY,自定义React组件ReactNodeTypes.COMPOSITE,DOM原生组件ReactNodeTypes.NATIVE this._renderedNodeType = ReactNodeTypes.getType(renderedElement); // 由ReactElement生成ReactComponent,这个方法在之前讲解过。根据不同type创建不同Component对象 // 参考 http://blog.csdn.net/u013510838/article/details/55669769 this._renderedComponent = this._instantiateReactComponent(renderedElement); // 递归渲染,渲染子组件 var markup = ReactReconciler.mountComponent(this._renderedComponent, transaction, nativeParent, nativeContainerInfo, this._processChildContext(context)); return markup; },
performInitialMount中先调用componentWillMount(),再将setState()产生的state改变进行state合并,然后调用_renderValidatedComponent()返回ReactElement,它会调用render()方法。然后由ReactElement创建ReactComponent。最后进行递归渲染。下面来看renderValidatedComponent()
_renderValidatedComponent: function () { var renderedComponent; ReactCurrentOwner.current = this; try { renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext(); } finally { ReactCurrentOwner.current = null; } !( return renderedComponent; }, _renderValidatedComponentWithoutOwnerOrContext: function () { var inst = this._instance; // 调用render方法,得到ReactElement。JSX经过babel转译后其实就是createElement()方法。这一点在前面也讲解过 var renderedComponent = inst.render(); return renderedComponent; },
3 存在期生命周期
组件实例对象已经生成时,我们可以通过setState()来更新组件。setState机制后面会有单独文章分析,现在只用知道它会调用updateComponent()来完成更新即可。下面来分析updateComponent
updateComponent: function(transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext ) { var inst = this._instance; var willReceive = false; var nextContext; var nextProps; // context对象如果有改动,则检查propTypes等,这在开发阶段可以报错提醒 if (this._context === nextUnmaskedContext) { nextContext = inst.context; } else { nextContext = this._processContext(nextUnmaskedContext); willReceive = true; } // 如果父元素类型相同,则跳过propTypes类型检查 if (prevParentElement === nextParentElement) { nextProps = nextParentElement.props; } else { nextProps = this._processProps(nextParentElement.props); willReceive = true; } // 调用componentWillReceiveProps,如果通过setState进入的updateComponent,则没有这一步 if (willReceive && inst.componentWillReceiveProps) { inst.componentWillReceiveProps(nextProps, nextContext); } // 提前合并state,componentWillReceiveProps中调用setState不会重新渲染,在此处做合并即可,因为后面也是要调用render的 // 这样可以避免没必要的渲染 var nextState = this._processPendingState(nextProps, nextContext); // 调用shouldComponentUpdate给shouldUpdate赋值 // 如果通过forceUpdate进入的updateComponent,即_pendingForceUpdate不为空,则不用判断shouldComponentUpdate. var shouldUpdate = true; if (!this._pendingForceUpdate && inst.shouldComponentUpdate) { shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } // 如果shouldUpdate为true,则会执行渲染,否则不会 this._updateBatchNumber = null; if (shouldUpdate) { this._pendingForceUpdate = false; // 执行更新渲染,后面详细分析 this._performComponentUpdate( nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext ); } else { // shouldUpdate为false,则不会更新渲染 this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; }},
updateComponent中,先调用componentWillReceiveProps,然后合并setState导致的state变化。然后调用shouldComponentUpdate判断是否需要更新渲染。如果需要,则调用_performComponentUpdate执行渲染更新,下面接着分析performComponentUpdate。
_performComponentUpdate: function(nextElement,nextProps,nextState,nextContext,transaction, unmaskedContext ) { var inst = this._instance; // 判断是否已经update了 var hasComponentDidUpdate = Boolean(inst.componentDidUpdate); var prevProps; var prevState; var prevContext; if (hasComponentDidUpdate) { prevProps = inst.props; prevState = inst.state; prevContext = inst.context; } // render前调用componentWillUpdate if (inst.componentWillUpdate) { inst.componentWillUpdate(nextProps, nextState, nextContext); } // state props等属性设置到内部变量inst上 this._currentElement = nextElement; this._context = unmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; // 内部会调用render方法,重新解析ReactElement并得到HTML this._updateRenderedComponent(transaction, unmaskedContext); // render后调用componentDidUpdate if (hasComponentDidUpdate) { transaction.getReactMountReady().enqueue( inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), inst ); }},
_performComponentUpdate会调用componentWillUpdate,然后在调用updateRenderedComponent进行更新渲染,最后调用componentDidUpdate。下面来看看updateRenderedComponent中怎么调用render方法的。
_updateRenderedComponent: function(transaction, context) { var prevComponentInstance = this._renderedComponent; var prevRenderedElement = prevComponentInstance._currentElement; // _renderValidatedComponent内部会调用render,得到ReactElement var nextRenderedElement = this._renderValidatedComponent(); // 判断是否做DOM diff。React为了简化递归diff,认为组件层级不变,且type和key不变(key用于listView等组件,很多时候我们没有设置type)才update,否则先unmount再重新mount if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { // 递归updateComponent,更新子组件的Virtual DOM ReactReconciler.receiveComponent(prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context)); } else { var oldNativeNode = ReactReconciler.getNativeNode(prevComponentInstance); // 不做DOM diff,则先卸载掉,然后再加载。也就是先unMountComponent,再mountComponent ReactReconciler.unmountComponent(prevComponentInstance, false); this._renderedNodeType = ReactNodeTypes.getType(nextRenderedElement); // 由ReactElement创建ReactComponent this._renderedComponent = this._instantiateReactComponent(nextRenderedElement); // mountComponent挂载组件,得到组件对应HTML var nextMarkup = ReactReconciler.mountComponent(this._renderedComponent, transaction, this._nativeParent, this._nativeContainerInfo, this._processChildContext(context)); // 将HTML插入DOM中 this._replaceNodeWithMarkup(oldNativeNode, nextMarkup, prevComponentInstance); }},_renderValidatedComponent: function() { var renderedComponent; ReactCurrentOwner.current = this; try { renderedComponent = this._renderValidatedComponentWithoutOwnerOrContext(); } finally { ReactCurrentOwner.current = null; } return renderedComponent;},_renderValidatedComponentWithoutOwnerOrContext: function() { var inst = this._instance; // 看到render方法了把,应该放心了把~ var renderedComponent = inst.render(); return renderedComponent;},
和mountComponent中一样,updateComponent也是用递归的方式将各子组件进行update的。这里要特别注意的是DOM diff。DOM diff是React中渲染加速的关键所在,它会帮我们算出virtual DOM中真正变化的部分,并对这部分进行原生DOM操作。为了避免循环递归对比节点的低效率,React中做了假设,即只对层级不变,type不变,key不变的组件进行Virtual DOM更新。这其中的关键是shouldUpdateReactComponent,下面分析
function shouldUpdateReactComponent(prevElement, nextElement) { var prevEmpty = prevElement === null || prevElement === false; var nextEmpty = nextElement === null || nextElement === false; if (prevEmpty || nextEmpty) { return prevEmpty === nextEmpty; } var prevType = typeof prevElement; var nextType = typeof nextElement; // React DOM diff算法 // 如果前后两次为数字或者字符,则认为只需要update(处理文本元素) // 如果前后两次为DOM元素或React元素,则必须在同一层级内,且type和key不变(key用于listView等组件,很多时候我们没有设置type)才update,否则先unmount再重新mount if (prevType === 'string' || prevType === 'number') { return (nextType === 'string' || nextType === 'number'); } else { return ( nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key ); }}
4 销毁
前面提到过,更新组件时,如果不满足DOM diff条件,会先unmountComponent, 然后再mountComponent,下面我们来分析下unmountComponent时都发生了什么事。和mountComponent的多态一样,不同type的ReactComponent也会有不同的unmountComponent行为。我们来分析下React自定义组件,也就是ReactCompositeComponent中的unmountComponent。
unmountComponent: function(safely) { if (!this._renderedComponent) { return; } var inst = this._instance; // 调用componentWillUnmount if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) { inst._calledComponentWillUnmount = true; // 安全模式下,将componentWillUnmount包在try-catch中。否则直接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; } // reset等待队列和其他等待状态 this._pendingStateQueue = null; this._pendingReplaceState = false; this._pendingForceUpdate = false; this._pendingCallbacks = null; this._pendingElement = null; // reset内部变量,防止内存泄漏 this._context = null; this._rootNodeID = null; this._topLevelWrapper = null; // 将组件从map中移除,还记得我们在mountComponent中将它加入了map中的吧 ReactInstanceMap.remove(inst); },
可见,unmountComponent还是比较简单的,它就做三件事
- 调用componentWillUnmount()
- 递归调用unmountComponent(),销毁子组件
- 将内部变量置空,防止内存泄漏
5 总结
React自定义组件创建期,存在期,销毁期三个阶段的生命周期调用上面都讲完了。三个入口函数mountComponent,updateComponent,unmountComponent尤其关键。大家如果有兴趣,还可以自行分析ReactDOMEmptyComponent,ReactDOMComponent和ReactDOMTextComponent的这三个方法。
深入学习React生命周期源码可以帮我们理清各个方法的调用顺序,明白它们都是什么时候被调用的,哪些条件下才会被调用等等。阅读源码虽然有点枯燥,但能够大大加深对上层API接口的理解,并体会设计者设计这些API的良苦用心。
- React源码分析4 — React生命周期详解
- React—组件生命周期详解
- React—组件生命周期详解
- React组件生命周期详解
- React组件生命周期详解
- React生命周期详解
- React-Native生命周期详解
- React组件生命周期详解
- React生命周期详解
- React-Native-源码分析
- react源码分析
- React源码分析3 — React组件插入DOM流程
- React源码分析7 — React合成事件系统
- React生命周期及事件详解
- React生命周期及事件详解
- React源码分析5 — setState机制
- React生命周期
- React 生命周期
- Delete Node in a Linked List
- 文件服务器-fastdfs
- 超简单将Centos的yum源更换为国内的阿里云源
- wsdl详解
- 常用CGI环境变量
- React源码分析4 — React生命周期详解
- Java线程同步示例
- java数组分析
- 解决adb not responding if youd like to retry...错误
- SQLServer 中发布与订阅
- ConcurrentHashMap总结
- 第十一讲项目四 玩数字
- 自定义函数名和命名空间存在的函数名同名的解决方法
- escape()、encodeURI()、encodeURIComponent()区别详解