理解ReactElement和ReactClass的概念
首先让我们理解两个概念:
ReactElement
一个描述DOM节点或component实例的字面级对象。它包含一些信息,包括组件类型type
和属性props
。就像一个描述DOM节点的元素(虚拟节点)。它们可以被创建通过React.createElement
方法或jsx
写法
分为DOM Element
和Component Elements
两类:
DOM Elements
当节点的type属性为字符串时,它代表是普通的节点,如div,span
@H_404_2@
- {
- 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
ReactClass是平时我们写的Component组件(类或函数),例如上面的Button
类。ReactClass实例化后调用render方法可返回DOM Element
。
react渲染过程
过程理解:
- // 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️⃣递归。
react更新机制
每个类型的元素都要处理好自己的更新:
自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。
text节点的更新很简单,直接更新文案。
-
浏览器基本元素的更新,分为两块:
第一步:调用this.setState
- 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方法
- 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,nextContext,nextUnmaskedContext
- );
- } else {
- // 不更新元素,但仍然设置props和state
- this._currentElement = nextParentElement;
- this._context = nextUnmaskedContext;
- inst.props = nextProps;
- inst.state = nextState;
- inst.context = nextContext;
- }
- // .......
- }
- // 内部_updateRenderedComponentWithNextElement方法
- ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() {
- // 判定两个element需不需要更新
- if (shouldUpdateReactComponent(prevRenderedElement,nextRenderedElement)) {
- // 如果需要更新,就继续调用子节点的receiveComponent的方法,传入新的element更新子节点。
- ReactReconciler.receiveComponent(
- prevComponentInstance,nextRenderedElement,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,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
- );
- }
- }
文本元素:
- 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
- );
- }
- }
- }
基本元素:
- ReactDOMComponent.prototype.receiveComponent = function(nextElement,context) {
- var prevElement = this._currentElement;
- this._currentElement = nextElement;
- this.updateComponent(transaction,context);
- }
- ReactDOMComponent.prototype.updateComponent = function(transaction,context) {
- // 略.....
- //需要单独的更新属性
- this._updateDOMProperties(lastProps,isCustomComponentTag);
- //再更新子节点
- this._updateDOMChildren(
- lastProps,context
- );
- // ......
- }
this._updateDOMChildren
方法内部调用diff算法,请看下一节........
react Diff算法
diff算法源码
- _updateChildren: function(nextNestedChildrenElements,context) {
- var prevChildren = this._renderedChildren;
- var removedNodes = {};
- var mountImages = [];
- // 获取新的子元素数组
- var nextChildren = this._reconcilerUpdateChildren(
- prevChildren,nextNestedChildrenElements,mountImages,removedNodes,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],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])
- );
- }
- }
- }
react的优点与总结
优点
@H_404_2@虚拟节点。在UI方面,不需要立刻更新视图,而是生成虚拟DOM后统一渲染。
组件机制。各个组件独立管理,层层嵌套,互不影响,react内部实现的渲染功能。
总结
想要更好的利用react的虚拟DOM,diff算法的优势,我们需要正确的优化、组织react页面。例如将一个页面render的ReactElement
节点分解成多个组件。在需要优化的组件手动添加 shouldComponentUpdate
来避免不需要的 re-render
。