React DOM Diff算法详解

React中文文档 doc.react-china.org/javascript

前端开发中,原生JS对DOM的频繁操做每每是使人头痛而且十分影响性能的; 而React在设计之初就考虑到了这种需求并将其做为重点,因而也就有了这里要说的Diff算法;能够说Diff是React性能的保障html

什么是Diff算法

Diff算法是一种经常使用的对比差别的算法,但传统Diff算法对于DOM树的比较是比较吃力的,下面附上一份简易版Diff算法(源代码来自:blog.csdn.net/qq_26708777…)前端

let result = [];
// 比较叶子节点
const diffLeafs = function (beforeLeaf, afterLeaf) {
    // 获取较大节点树的长度
    let count = Math.max(beforeLeaf.children.length, afterLeaf.children.length);
    // 循环遍历
    for (let i = 0; i < count; i++) {
        const beforeTag = beforeLeaf.children[i];
        const afterTag = afterLeaf.children[i];
        // 添加 afterTag 节点
        if (beforeTag === undefined) {
            result.push({ type: "add", element: afterTag });
            // 删除 beforeTag 节点
        } else if (afterTag === undefined) {
            result.push({ type: "remove", element: beforeTag });
            // 节点名改变时,删除 beforeTag 节点,添加 afterTag 节点
        } else if (beforeTag.tagName !== afterTag.tagName) {
            result.push({ type: "remove", element: beforeTag });
            result.push({ type: "add", element: afterTag });
            // 节点不变而内容改变时,改变节点
        } else if (beforeTag.innerHTML !== afterTag.innerHTML) {
            if (beforeTag.children.length === 0) {
                result.push({
                    type: "changed",
                    beforeElement: beforeTag,
                    afterElement: afterTag,
                    html: afterTag.innerHTML
                });
            } else {
                // 递归比较
                diffLeafs(beforeTag, afterTag);
            }
        }
    }
    return result;
}

复制代码

React中的Diff

既然传统的Diff算法对于DOM操做有性能上的缺点,那么React又是如何解决这一问题的呢?下面看一段React代码java

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
	render() {
		return (
			<div className="app"> <h1>Find My Fruit</h1> </div>
		)
	}
}

ReactDOM.render(
    <App />, document.getElementById('root') ); 复制代码

须要特别注意, render 执行的结果获得的不是真正的 DOM 节点. 结果仅仅是轻量级的 JavaScript 对象, 咱们称之为 virtual DOM.react

Diff策略

· Web UI 中DOM节点跨层级的移动操做特别少,能够忽略不计算法

· 拥有相同类的两个组件将会生成类似的树形结构,拥有不一样类的两个组件将会生成不一样的树形结构。app

· 对于同一层级的一组子节点,它们能够经过惟一id进行区分。dom

tree diff

基于策略一,WebUI中DOM节点跨层级的移动操做少的能够忽略不计,React对Virtual DOM树进行了层级控制,只会对相同层级的DOM节点进行比较,即同一个父元素下的全部子节点,当发现节点已经不存在了,则会删除掉该节点下全部的子节点,不会再进行比较。这样只须要对DOM树进行一次遍历,就能够完成整个树的比较。复杂度变为O(n);性能

那么,若是真的出现跨层及操做,React是如何表现的呢? 以下图所示,A节点及其子节点被整个移动到D节点下面去,因为React只会简单的考虑同级节点的位置变换,而对于不一样层级的节点,只有建立和删除操做,因此当根节点发现A节点消失了,就会删除A节点及其子节点,当D发现多了一个子节点A,就会建立新的A做为其子节点。 此时,diff的执行状况是:ui

remove A-> add A-> add B ->add C

这种状况天然是比较消耗性能的,因此在开发React组件时要尽可能保证的DOM结构的稳定,尽可能避免出现跨层级操做

Components diff

React 只会匹配相同 class 的 component.说白了也就是只会对同一类型的组件进行比较,而对于不一样的类型组件,则放弃比较,直接删掉旧的添加新的

好比, 若是有个 Header 被 ExampleBlock 替换掉了, React 会删除掉 header 再建立一个 example block. 咱们不须要化宝贵的时间去匹配两个不大可能有类似之处的 component.

列表 diff

假设咱们有个 component, 一个循环渲染了 5 个 component, 随后又在列表中间插入一个新的 component.这时候会如何操做呢?

默认操做以下:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是否是很没有效率?

因此咱们须要使用key来给每一个节点作一个惟一的标识,Diff算法就能够正确的识别此节点,找到正确的位置区插入新的节点。同一个列表由旧变新有三种行为,插入、移动和删除,它的比较策略是对于每个列表指定key,先将全部列表遍历一遍,肯定要新增和删除的,再肯定须要移动的。如图所示,第一步将D删掉,第二步增长E,再次执行时A和B只须要移动位置便可
相关文章
相关标签/搜索