react 从入门到实践之 react 的虚拟 DOM 和 Diff 算法

一、react 的虚拟 DOM 和 Diff 算法

  1. 虚拟 DOMdiff算法是 React中非常核心的两个概念, 我们需要对此有一个很全面的认知。这对于我们用脚手架开发项目, 尤其是企业中前后端分离的项目(类似: 后台管理系统)等有很大的帮助。

  2. 对于虚拟 DOM的内部执行流程,如下所示:

  • JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中;
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;
  • 把步骤 2 所记录的差异应用到步骤 1 所构建的真正的 DOM树上,视图就更新了。
  1. 对于虚拟DOM的原理剖析,如下所示:
  • Virtual DOM 本质上就是在 JSDOM 之间做了一个缓存。可以类比 CPU 和硬盘,硬盘读取速度比较慢,我们会就在它们之间加缓存条;
  • 反之, 既然 DOM 运行速度慢,那么我们就在 JSDOM 之间加个缓存。JS 只操作 Virtual DOM,最后的时候再把变更的结果写入 DOM
  1. 对于 diff算法,如下所示:
  • 如果两棵树的根元素类型不同,React会销毁旧树,创建新树
  • 对于类型相同的React DOM 元素,React会对比两者的属性是否相同,只更新不同的属性; 当处理完这个 DOM 节点,React就会递归处理子节点。
  • 遍历插入元素, 如果没有 key, React将改变每一个子删除重新创建; 为了解决这个问题,React提供了一个 key 属性。当子节点带有key属性,React会通过 key 来匹配原始树和后来的树。
  1. 对于 diff 算法的执行过程,通过绑定 key, React就知道带有key '1024' 的元素是新的,对于 '1025''1026' 仅仅移动位置即可。

  2. 对于其中 key 的使用注意,如下所示:

  • key 属性只会在 React 内部使用,不会传递给组件
  • 在遍历数据时,推荐在组件中使用 key 属性,<li key={obj.id}>{obj.t}</li>
  • key 只需要保持与他的兄弟节点唯一即可,不需要全局唯一
  • 尽可能的减少数组 Index 作为 key,数组中插入元素的等操作时,会降低效率
  1. 对于 react 的虚拟 DOMDiff算法,可应用于九宫格案例,代码如下所示:
 <script type="text/babel">
    class FlexView extends React.Component {
      constructor (props) {
        super(props)
        this.state = {
          shopArr: []
        }
      }

      static defaultProps = {
        dataArr: [
          {
              "icon": "f1",
              "name": "番茄"
          },
          {
              "icon": "f2",
              "name": "苹果"
          },
          {
              "icon": "f3",
              "name": "水蜜桃"
          },
          {
              "icon": "f4",
              "name": "香蕉"
          },
          {
              "icon": "f5",
              "name": "蓝莓"
          },
          {
              "icon": "f6",
              "name": "菠萝"
          },
          {
              "icon": "f7",
              "name": "草莓"
          },
          {
              "icon": "f8",
              "name": "猕猴桃"
          },
          {
              "icon": "f9",
              "name": "橙子"
          }
        ]
      }

      render () {
        return (
          <div className="box">
            <div className="top">
              <button onClick={() => this._addShop()}>Add</button>
              <button onClick={() => this._removeShop()}>Remove</button>
            </div>
            <div className="bottom">
              {this.state.shopArr}
            </div>
          </div>
        )
      }

      // 添加商品的方法
      _addShop () {
        // 相关的变量
        const cols = 3, shopW = 100, shopH = 120, width = 320, height = 420
        // 取出数据
        const { dataArr } = this.props
        // 取出下标
        const index = this.state.shopArr.length
        if (index >= 9) {
          alert('已经买了很多水果,不能再买了')
          return
        }
        // 求出子组件的行和列
        const row = Math.floor(index / cols)
        const col = Math.floor(index % cols)
        // 求出当前的盒子的left和top
        const xSpace = (width - cols * shopW) / (cols - 1)
        const ySpace = (height - 3 * shopW) / 2
        const left = col * (shopW + xSpace)
        const top = row * (shopH + ySpace)
        // 创建子组件装入数组
        const shopView = (
          <div className="item" style={{left, top}} key={index}>
              <img src={'images/' + dataArr[index].icon + '.png'}
                style={{width: shopW * 0.8, height:shopW*0.8}}
              />
              <span>{dataArr[index].name}</span>
          </div>
        )
        // 更新状态
        const tempArr = this.state.shopArr
        tempArr.push(shopView)
        this.setState({
          shopArr: tempArr
        })
      }

      // 删除商品的方法
      _removeShop () {
        const tempArr = this.state.shopArr
        if (tempArr.length === 0) {
          alert('购物车空空如也')
          return
        }
        tempArr.pop()
        this.setState({
          shopArr: tempArr
        })
      }
    }

    // 渲染组件
    ReactDOM.render(<FlexView />, document.getElementById('app'))
  </script>
上一篇:带卡片的input输入框


下一篇:前端免费图标icon的使用方法和获取