React源码解析
- 生命周期
当首次挂载组件时,按顺序执行 getDefaultProps、getInitialState、componentWillMount、 render 和 componentDidMount。
当卸载组件时,执行 componentWillUnmount。
当重新挂载组件时,此时按顺序执行 getInitialState、componentWillMount、render 和 componentDidMount,但并不执行 getDefaultProps。
当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceiveProps、 shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate
- 创建自定义组件
class MyComponent extends React.Component 其实就 是调用内部方法 createClass 创建组件
调用getDefaultProps。
由于 getDefaultProps 是通过构造函数进行管理的,所以也是整个生命周期中先开始执行的
- MOUNTING(加载组件)
若存在 componentWillMount,则执行。如果此时在 componentWillMount 中调用 setState 方法,不会触发re-render,会进行state合并,并且this.state不是最新的。
- RECEIVE_PROPS(更新组件)
如果此时在 componentWillReceiveProps 中调 用 setState,是不会触发 re-render的,而是会进行 state 合并。
updateComponent 本质上也是通过递归渲染内容的,由于递归的特性,父组件的 component- WillUpdate 是在其子组件的 componentWillUpdate 之前调用的,而父组件的 componentDidUpdate 也是在其子组件的 componentDidUpdate 之后调用的
禁止在 shouldComponentUpdate 和 componentWillUpdate 中调用 setState,这会造成循环调用,直至耗光浏览器内存后崩溃
- UNMOUNTING(卸载组件)
如果存在 componentWillUnmount,则执行并重置所有相关参数、更新队列以及更新状态,如 果此时在 componentWillUnmount 中调用 setState,是不会触发 re-render 的,这是因为所有更新 队列和更新状态都被重置为 null
无状态组件
只有一个render方法
- setState
调用setState -> 新state进入队列 -> 合并更新队列 -> 判断是否在批量更新 -> 如果在,component放入dirtyComponent等待下一次更新;如果不在,进行批量更新
ReactComponent.prototype.setState = function(partialState, callback) { this.updater.enqueueSetState(this, partialState); //更新state if (callback) {//回调函数 this.updater.enqueueCallback(this, callback, 'setState'); } };
enqueueSetState中,合并更新队列,调用enqueueUpdate
function enqueueUpdate(component) { // 不处于批量更新模式,进行更新 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); //批处理更新 return; } // 处于批量更新模式 dirtyComponents.push(component); }
事务(Transaction)
事务就是将需要执行的方法使用 wrapper 封装起来,再通过事务提供的 perform 方法执行。 而在 perform 之前,先执行所有 wrapper 中的 initialize 方法,执行完 perform 之后(即执行 method 方法后)再执行所有的 close 方法。一组 initialize 及 close 方法称为一个 wrapper。
而要使用事务的模 块,除了需要把 mixin 混入自己的事务实现中外,还要额外实现一个抽象的 getTransactionWrappers 接口。这个接口用来获取所有需要封装的前置方法(initialize)和收尾方法(close), 因此它需要返回一个数组的对象,每个对象分别有 key 为 initialize 和 close 的方法perform (func, scope,a,b,c,d,e,f){ this.initializeAll(0); method.call(scope, a, b, c, d, e, f); this.closeAll(0); }
- Diff算法
Diff算法本质上是对javascript对象之间的比较,只有在React更新阶段(调用了setState)才会有Diff算法的运用。
流程:
- this.setState(partialState) 更新state
- this.replaceState(completeState) 合并state
- this._receivePropsAndState(this.props,nextState,transaction)收到新的props和state,决定是否更新组件
_receivePropsAndState: function(nextProps, nextState, transaction)
if (!this.shouldComponentUpdate || //没有定义shouldComponentUpdate函数 this.shouldComponentUpdate(nextProps, nextState)) { //shouldComponentUpdate函数返回true this._performComponentUpdate(nextProps, nextState, transaction); } else { //shouldComponentUpdate函数返回了false,不进行DOM更新,只更新props和state的值 this.props = nextProps; this.state = nextState; }
4.this._performComponentUpdate(nextProps, nextState, transaction);
调用this.componentWillUpdate
this.updateComponent(transaction);
调用this.componentDidUpdate
5.this.updateComponent(transaction)
updateComponent: function(transaction) { var currentComponent = this._renderedComponent; //原组件 var nextComponent = this._renderValidatedComponent(); //新组件 //两个组件的类相同(构造函数一样) if (currentComponent.constructor === nextComponent.constructor) { if (!nextComponent.props.isStatic) { currentComponent.receiveProps(nextComponent.props, transaction); //更新组件 } } else { // 两个组件的类不一样,直接替换 var thisID = this._rootNodeID; var currentComponentID = currentComponent._rootNodeID; //卸载原组件 currentComponent.unmountComponent(); //加载新组件 var nextMarkup = nextComponent.mountComponent(thisID, transaction); ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID( currentComponentID, nextMarkup ); this._renderedComponent = nextComponent; }
}
- currentComponent.receiveProps(nextComponent.props, transaction)
有三种类型的component:
①文本 ReactTextComponent
receiveProps: function(nextProps, transaction) { //text不一样直接替换 if (nextProps.text !== this.props.text) { this.props.text = nextProps.text; ReactComponent.DOMIDOperations.updateTextContentByID( this._rootNodeID, nextProps.text ); } }
②React自定义组件
调用componentWillReceiveProps
再次调用this._receivePropsAndState(nextProps, nextState, transaction);
tree diff
比较两棵DOM树,如果某个节点不存在,则该节点及其子节点会被完全删除,不会进一步比较。 React只会简单地考虑同层级节点的位置变换
component diff
如果是同一类型组件,继续比较
如果不是,替换整个组件
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那 么就可以节省大量的 diff 运算时间。因此,React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff 算法分析。
element diff
1.比较新旧集合元素的key,如果有相同key,说明旧集合中有新集合的元素。
2.如果该元素在旧集合的index < lastIndex (lastindex指的是访问过的元素在旧集合中最大的index),移动该元素到nextIndex,否则不移动。
3.如果新集合里的元素在旧集合不存在,创建新元素到当前index。
4.更新lastIndex, nextIndex++
存在的缺陷
《深入React技术栈》作者观点是:
如果旧集合是A,B,C,D, 新集合是D,A,B,C
D不会移动,而ABC都要依次移动。
实际上D只要移动到C后面
本人认为:A,B,C的index都已经发生变化,所以肯定会有移动操作,避免不了。