原文连接javascript
理解ReactElement和ReactClass的概念java
ReactElementnode
ReactClassreact
react渲染过程git
react更新机制github
首先让咱们理解两个概念:浏览器
一个描述DOM节点或component实例的字面级对象。它包含一些信息,包括组件类型type
和属性props
。就像一个描述DOM节点的元素(虚拟节点)。它们能够被建立经过React.createElement
方法或jsx
写法dom
分为DOM Element
和Component Elements
两类:
DOM Elements
当节点的type属性为字符串时,它表明是普通的节点,如div,span
{ type: 'button', props: { className: 'button button-blue', children: { type: 'b', props: { children: 'OK!' } } } }
Component Elements
当节点的type属性为一个函数或一个类时,它表明自定义的节点
class Button extends React.Component { render() { const { children, color } = this.props; return { type: 'button', props: { className: 'button button-' + color, children: { type: 'b', props: { children: children } } } }; } } // Component Elements { type: Button, props: { color: 'blue', children: 'OK!' } }
ReactClass是平时咱们写的Component组件(类或函数),例如上面的Button
类。ReactClass实例化后调用render方法可返回DOM Element
。
过程理解:
// element是 Component Elements ReactDOM.render({ type: Form, props: { isSubmitted: false, buttonText: 'OK!' } }, document.getElementById('root'));
调用React.render
方法,将咱们的element
根虚拟节点渲染到container
元素中。element
能够是一个字符串文本元素,也能够是如上介绍的ReactElement
(分为DOM Elements, Component Elements)。
根据element
的类型不一样,分别实例化ReactDOMTextComponent
, ReactDOMComponent
, ReactCompositeComponent
类。这些类用来管理ReactElement
,负责将不一样的ReactElement
转化成DOM(mountComponent方法),负责更新DOM(receiveComponent方法,updateComponent方法, 以下会介绍)等。
ReactCompositeComponent
实例调用mountComponent
方法后内部调用render
方法,返回了DOM Elements
。再对如图的步骤2️⃣递归。
每一个类型的元素都要处理好本身的更新:
自定义元素的更新,主要是更新render出的节点,作甩手掌柜交给render出的节点的对应component去管理更新。
text节点的更新很简单,直接更新文案。
浏览器基本元素的更新,分为两块:
先是更新属性,对比出先后属性的不一样,局部更新。而且处理特殊属性,好比事件绑定。
而后是子节点的更新,子节点更新主要是找出差别对象,找差别对象的时候也会使用上面的shouldUpdateReactComponent来判断,若是是能够直接更新的就会递归调用子节点的更新,这样也会递归查找差别对象。不可直接更新的删除以前的对象或添加新的对象。以后根据差别对象操做dom元素(位置变更,删除,添加等)。
ReactClass.prototype.setState = function(newState) { //this._reactInternalInstance是ReactCompositeComponent的实例 this._reactInternalInstance.receiveComponent(null, newState); }
这里主要分三种状况,文本元素,基本元素,自定义元素。
receiveComponent方法源码
// receiveComponent方法 ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) { var prevElement = this._currentElement; var prevContext = this._context; this._pendingElement = null; this.updateComponent( transaction, prevElement, nextElement, prevContext, nextContext ); }
updateComponent方法源码
// updateComponent方法 ReactCompositeComponent.prototype.updateComponent = function( transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext ) { // 简写..... // 不是state更新而是props更新 if (prevParentElement !== nextParentElement) { willReceive = true; } if (willReceive && inst.componentWillReceiveProps) { // 调用生命周期componentWillReceiveProps方法 } // 是否更新元素 if (inst.shouldComponentUpdate) { // 若是提供shouldComponentUpdate方法 shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); } else { if (this._compositeType === CompositeTypes.PureClass) { // 若是是PureClass,浅层对比props和state shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState); } } if (shouldUpdate) { // 更新元素 this._performComponentUpdate( nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext ); } else { // 不更新元素,但仍然设置props和state this._currentElement = nextParentElement; this._context = nextUnmaskedContext; inst.props = nextProps; inst.state = nextState; inst.context = nextContext; } // ....... }
内部_performComponentUpdate方法源码
// 内部_updateRenderedComponentWithNextElement方法 ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() { // 断定两个element需不须要更新 if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) { // 若是须要更新,就继续调用子节点的receiveComponent的方法,传入新的element更新子节点。 ReactReconciler.receiveComponent( prevComponentInstance, nextRenderedElement, transaction, this._processChildContext(context) ); } else { // 卸载以前的子节点,安装新的子节点 var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance); ReactReconciler.unmountComponent( prevComponentInstance, safely, false /* skipLifecycle */ ); var nodeType = ReactNodeTypes.getType(nextRenderedElement); this._renderedNodeType = nodeType; var child = this._instantiateReactComponent( nextRenderedElement, nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */ ); this._renderedComponent = child; var nextMarkup = ReactReconciler.mountComponent( child, transaction, this._hostParent, this._hostContainerInfo, this._processChildContext(context), debugID ); }
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') { // 若是先前的ReactElement对象类型是字符串或数字,新的ReactElement对象类型也是字符串或数字,则须要更新,新的ReactElement对象类型是对象,则不该该更新,直接替换。 return (nextType === 'string' || nextType === 'number'); } else { // 若是先前的ReactElement对象类型是对象,新的ReactElement对象类型也是对象,而且标签类型和key值相同,则须要更新 return ( nextType === 'object' && prevElement.type === nextElement.type && prevElement.key === nextElement.key ); } }
receiveComponent方法源码
ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) { //跟之前保存的字符串比较 if (nextText !== this._currentElement) { this._currentElement = nextText; var nextStringText = '' + nextText; if (nextStringText !== this._stringText) { this._stringText = nextStringText; var commentNodes = this.getHostNode(); // 替换文本元素 DOMChildrenOperations.replaceDelimitedText( commentNodes[0], commentNodes[1], nextStringText ); } } }
receiveComponent方法源码
ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) { var prevElement = this._currentElement; this._currentElement = nextElement; this.updateComponent(transaction, prevElement, nextElement, context); }
updateComponent方法源码
ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) { // 略..... //须要单独的更新属性 this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag); //再更新子节点 this._updateDOMChildren( lastProps, nextProps, transaction, context ); // ...... }
this._updateDOMChildren
方法内部调用diff算法,请看下一节........
diff算法源码
_updateChildren: function(nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var removedNodes = {}; var mountImages = []; // 获取新的子元素数组 var nextChildren = this._reconcilerUpdateChildren( prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context ); if (!nextChildren && !prevChildren) { return; } var updates = null; var name; var nextIndex = 0; var lastIndex = 0; var nextMountIndex = 0; var lastPlacedNode = null; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { // 同一个引用,说明是使用的同一个component,因此咱们须要作移动的操做 // 移动已有的子节点 // NOTICE:这里根据nextIndex, lastIndex决定是否移动 updates = enqueue( updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) ); // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 更新component的.mountIndex属性 prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 更新lastIndex lastIndex = Math.max(prevChild._mountIndex, lastIndex); } // 添加新的子节点在指定的位置上 updates = enqueue( updates, this._mountChildAtIndex( nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context ) ); nextMountIndex++; } // 更新nextIndex nextIndex++; lastPlacedNode = ReactReconciler.getHostNode(nextChild); } // 移除掉不存在的旧子节点,和旧子节点和新子节点不一样的旧子节点 for (name in removedNodes) { if (removedNodes.hasOwnProperty(name)) { updates = enqueue( updates, this._unmountChild(prevChildren[name], removedNodes[name]) ); } } }
虚拟节点。在UI方面,不须要马上更新视图,而是生成虚拟DOM后统一渲染。
组件机制。各个组件独立管理,层层嵌套,互不影响,react内部实现的渲染功能。
差别算法。根据基本元素的key值,判断是否递归更新子节点,仍是删除旧节点,添加新节点。
想要更好的利用react的虚拟DOM,diff算法的优点,咱们须要正确的优化、组织react页面。例如将一个页面render的ReactElement
节点分解成多个组件。在须要优化的组件手动添加 shouldComponentUpdate
来避免不须要的 re-render
。