React 更新视图过程
真实的 setState 的过程: setState( partialState ) { // 1. 通过组件对象获取到渲染对象 var internalInstance = ReactInstanceMap.get(publicInstance); // 2. 把新的状态放在渲染对象的 _pendingStateQueue 里面 internalInstance._pendingStateQueue.push( partialState ) // 3. 查看下是否正在批量更新 // 3.1. 如果正在批量更新,则把当前这个组件认为是脏组件,把其渲染对象保存到 dirtyComponents 数组中 // 3.2. 如果可以批量更新,则调用 ReactDefaultBatchingStrategyTransaction 开启更新事务,进行真正的 vdom diff。 // | // v // internalInstance.updateComponent( partialState ) } updateComponent 方法的说明: updateComponent( partialState ) { // 源码中 partialState 是从 this._pendingStateQueue 中获取的,这里简化了状态队列的东西,假设直接从外部传入 var inst = this._instance; var nextState = Object.assign( {},inst.state,partialState ); // 获得组件对象,准备更新,先调用生命周期函数 // 调用 shouldComponentUpdate 看看是否需要更新组件(这里先忽略 props 和 context的更新) if ( inst.shouldComponentUpdate(inst.props,nextState,nextContext) ) { // 更新前调用 componentWillUpdate isnt.componentWillUpdate( inst.props,nextContext ); inst.state = nextState; // 生成新的 vdom var nextRenderedElement = inst.render(); // 通过上一次的渲染对象获取上一次生成的 vdom var prevComponentInstance = this._renderedComponent; // render 中的根节点的渲染对象 var prevRenderedElement = prevComponentInstance._currentElement; // 上一次的根节点的 vdom // 通过比较新旧 vdom node 来决定是更新 dom node 还是根据最新的 vdom node 生成一份真实 dom node 替换掉原来的 if ( shouldUpdateReactComponent(prevRenderedElement,nextRenderedElement) ) { // 更新 dom node prevComponentInstance.receiveComponent( nextRenderedElement ) } else { // 生成新的 dom node 替换原来的(以下是简化版,只为了说明流程) var oldHostNode = ReactReconciler.getHostNode( prevComponentInstance ); // 根据新的 vdom 生成新的渲染对象 var child = instantiateReactComponent( nextRenderedElement ); this._renderedComponent = child; // 生成新的 dom node var nextMarkup = child.mountComponent(); // 替换原来的 dom node oldHostNode.empty(); oldHostNode.appendChild( nextMarkup ) } } } 接下来看下 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; if (prevType === 'string' || prevType === 'number') { return (nextType === 'string' || nextType === 'number'); } else { return ( nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key ); } } 基本的思路就是比较当前 vdom 节点的类型,如果一致则更新,如果不一致则重新生成一份新的节点替换掉原来的。好了回到刚刚跟新 dom node这条路 prevComponentInstance.receiveComponent( nextRenderedElement ),即 render 里面根元素的渲染对象的 receiveComponent 方法做了最后的更新 dom 的工作。如果根节点的渲染对象是组件即 ReactCompositeComponent.receiveComponent,如果根节点是内置对象(html 元素)节点即 ReactDOMComponent.receiveComponent。ReactCompositeComponent.receiveComponent 最终还是调用的上面提到的 updateComponent 循环去生成 render 中的 vdom,这里就先不深究了。最终 html dom node 的更新策略都在 ReactDOMComponent.receiveComponent 中。 class ReactDOMComponent { // @param {nextRenderedElement} 新的 vdom node receiveComponent( nextRenderedElement ) { var prevElement = this._currentElement; this._currentElement = nextRenderedElement; var lastProps = prevElement.props; var nextProps = this._currentElement.props; var lastChildren = lastProps.children; var nextChildren = nextProps.children; /* 更新 props _updateDOMProperties 方法做了下面两步 1. 记录下 lastProps 中有的,nextProps 没有的,删除 2. 记录下 nextProps 中有的,且与 lastProps中不同的属性,setAttribute 之 */ this._updateDOMProperties(lastProps,nextProps,transaction); /* 迭代更新子节点,源代码中是 this._updateDOMChildren(lastProps,transaction,context); 以下把 _updateDOMChildren 方法展开,对于子节点类型的判断源码比较复杂,这里只针对string|number和非string|number做一个简单的流程示例 */ // 1. 如果子节点从有到无,则删除子节点 if ( lastChildren != null && nextChildren == null ) { if ( typeof lastChildren === 'string' | 'number' /* 伪代码 */ ) { this.updateTextContent(''); } else { this.updateChildren( null,context ); } } // 2. 如果新的子节点相对于老的是有变化的 if ( nextChildren != null ) { if ( typeof lastChildren === 'string' | 'number' && lastChildren !== nextChildren /* 伪代码 */ ) { this.updateTextContent('' + nextChildren); } else if ( lastChildren !== nextChildren ) { this.updateChildren( nextChildren,context ); } } } }
先来看最简单的情况: 再来看两个例子: 例C: 我们看到对于例C来说其实最便利的方法就是把 span 插入到第二的位置上,然后其他div只要做 attr 的更新而不需要再进行位置的增删,如果 attr 都没有变化,那么后两个 div 根本不需要变化。但是按例A里面的算法,我们需要进行好几步的 dom 操作。这是为算法减少时间复杂度,做了妥协。但是 react 对节点引入了 key 这个关键属性帮助优化这种情况。假设我们给所有节点都添加了唯一的 key 属性,如下面例D: 但是 react 的这种算法的优化也带来了一种极端的情况: 但是其实这里只需要更新每个节点的 attr,他们的位置根本不需要做变化。所以如果要给元素指定 key 最好避免元素的位置有太多太大的跃迁变化。 基本上 setState 之后到最终的 dom 变化的过程就是这么结束了。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |