返回顶部
首页 > 资讯 > 前端开发 > JavaScript >react diff 算法实现思路及原理解析
  • 480
分享到

react diff 算法实现思路及原理解析

2024-04-02 19:04:59 480人浏览 安东尼
摘要

目录事例分析diff 特点diff 思路实现 diff 算法修改入口文件实现 React.Fragment我们需要修改 children 对比前面几节我们学习了解了 react 的渲

前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有我们常见的 key 的作用。

diff 算法使用在子都是数组的情况下,这点和 Vue 是一样的。如果元素是其他类型的话直接替换就好。

事例分析

按照之前的 diff 写法,如果元素不同我们是直接删了 a 再插入的:

按照上面图的结构,我们需要知道那个元素变化了,其实右边相对左边只是把 A 做了移动,没有 dom 元素的删除和新增。

diff 特点

  • 同级对比 On
  • 类型不一样销毁老的,创建新的
  • 通过 key 标识

key 这里需要标识,主要是为了列表中有删除新增时有优化效果,如果纯静态列表,只是展示作用,key 意义不大。

diff 思路

  • 使用 map 存储节点状态,格式如下:
let map = {
  keyA: ADOM,
  keyB: BDOM
}
  • 定义 lastPlacedIndex 记录上一个不需要移动的老节点

默认 lastPlacedIndex = 0 ,上一个不需要移动的节点,在循环新的子虚拟 dom 时,如果老节点的挂载索引小于当前值,则改变 lastPlacedIndex。这里有点类似 vue 的最长递增子序列,最大的保证不变的 dom 元素,只是判断方式不同。

  • 循环新数组
  • 先出 Amap 中如果有 A,表示可以复用
    • 判断 A 的老挂载索引和 lastPlacedIndex 对比,如果索引值大,A 节点不需要移动,更新 lastPlacedIndex 的值;否则循环到 B,挂载索引小,需要移动 B;循环到 Gmap 中没有值,需要新增;新的数组节点循环完,未用到的老节点全部删除。

实现 diff 算法

修改入口文件

// src/index.js
class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {list: ['A','B', 'C', 'D', 'E', 'F']}
  }
  handleClick = () => {
    this.setState({
      list: ['A', 'C', 'E', 'B', 'G']
    })
  }
  render() {
    // 使用空标签
    return <React.Fragment>
      <ul>
      {this.state.list.map(item => {
        // 这里使用 key 标识
        return <li key={item}>{item}</li>
      })}
      </ul>
      <button onClick={this.handleClick}>add 1</button>
    </React.Fragment>
  }
}

实现 React.Fragment

Fragment 就是代码片段,不占用 dom 结构。简写 <></>,对应 dom 操作为 createDocumentFragment

  • 是用原生库打印,看结构

可以发现就是一个简单的 Symbol,所以需要定义新的类型:

为什么一个简单的 Symbol 可以被渲染成片段呢?依赖于 babel 解析。

// src/constants.js
export const REACT_FRAGMENT = Symbol("react.fragment") // React.Fragment 标签
// 备用,diff 时做 patch 的 type 定义
// 新的插入
export const PLACEMENT = 'PLACEMENT'
// 复用的移动
export const MOVE = 'MOVE'

在创建元素的时候进行类型判断,记得 react.js 中导出

// src/react-dom.js  
// createDOM 方法
else if (type === REACT_FRAGMENT) {
  // fragment 片段
  dom = document.createDocumentFragment()
}
// updateElement 方法
else if (oldVdom.type === REACT_FRAGMENT) {
  // fragment 不需要对比,直接对比 子 就可以了
  const currentDOM = newVdom.dom = findDOM(oldVdom)
    updateChildren(currentDOM, oldVdom.props.children, newVdom.props.children)
}

我们需要修改 children 对比

之前逻辑:

// src/react-dom.js
// diff  没有做复用,直接做的替换
function updateChildren(parentDOM, oldVChildren, newVChildren) {
  // 拿到最长的
  let maxLength = Math.max(oldVChildren.length, newVChildren.length);
  for (let i = 0; i < maxLength; i++) {
  // 不能直接 appendChild 进父,需要找到当前操作的节点的下一个节点。在其前面插入
    const nextVdom = oldVChildren.find((item, index) => index > i && item && findDOM(item))
    compareTwoVdom(parentDOM, oldVChildren[i], newVChildren[i], findDOM(nextVdom));
  }
}

新的逻辑(参考上面的流程):

// diff
function updateChildren(parentDOM, oldVChildren, newVChildren) {
  oldVChildren = Array.isArray(oldVChildren) ? oldVChildren : [oldVChildren];
  newVChildren = Array.isArray(newVChildren) ? newVChildren : [newVChildren];

 // 1.循环老结构, 构建map存储  key: dom
  const keydOldMap = {}
  let lastPlacedIndex = 0
  oldVChildren.forEach((oldVChild, index) => {
    let oldKey = oldVChild?.key || index //  写key 了就用key,没写默认 index
    keydOldMap[oldKey] = oldVChild
  })
  // 2. 创建 dom 补丁包,收集 dom 操作
  const patch = []
  newVChildren.forEach((newVChild, index) => {
    newVChild.mountIndex = index // 为新元素每个添加索引标识
    const newKey = newVChild?.key || index
    const oldVChild = keydOldMap[newKey] // 看有没有存
    if(oldVChild) {
      // 如果有老的,就去更新老节点 这里直接可以复用
      updateElement(findDOM(oldVChild).parentnode, oldVChild, newVChild)
      if(oldVChild.mountIndex < lastPlacedIndex) {
        patch.push({
          type: MOVE,
          oldVChild,
          newVChild,
          mountIndex: index // 旧的移动到新的的位置
        })
      }
      // 复用过了 删除掉
      delete keydOldMap[newKey]
      lastPlacedIndex = Math.max(lastPlacedIndex, oldVChild.mountIndex)// 取最大
    } else {
      // 新的
      patch.push({
        type: PLACEMENT,
        newVChild,
        mountIndex: index
      })
    }
  })
  // 找到需要移动的老节点
  const moveVChildren = patch.filter(action => action.type === MOVE).map(action => action.oldVChild)
  // 把要删除的节点 和  要移动的节点先全删除     (页面里没有了,但是内存中还存在  patch 中有存)
  Object.values(keydOldMap).concat(moveVChildren).forEach(oldVdom => {
    let currentDOM = findDOM(oldVdom)
    currentDOM.remove()
  })
  patch.forEach(action => {
    const {type, oldVChild, newVChild, mountIndex} = action
    // 老的真实子节点
    const childNodes = parentDOM.childNodes
    // 新的插入
    if (type === PLACEMENT) {
      let newDOM = createDOM(newVChild)
      let childNode = childNodes[mountIndex] // 老真实节点
      if (childNode) {
        // 往 老的父对应位置插入
        parentDOM.insertBefore(newDOM, childNode)
      } else {
        parentDOM.appendChild(newDOM)
      }
    } else if (type === MOVE) {
      // 移动不用创建 新 dom,复用
      let oldDOM = findDOM(oldVChild)
      let childNode = childNodes[mountIndex] // 老真实节点
      if (childNode) {
        // 往 老的父对应位置插入
        parentDOM.insertBefore(oldDOM, childNode)
      } else {
        parentDOM.appendChild(oldDOM)
      }
    }
  })
}

实现如下跟原生一致,可以看到,三个节点实现了复用,即 A, C, E

如果没有写 key,我们在看效果:

可以看到只有第一个节点实现了复用,因为默认索引都使用的 0。所以这也是为什么不建议我们使用索引当 key 的原因。动态列表 key 意义不大。

本节代码不是很多,主要是 diff 算法的思路和实现原理。如果了解了 vuediff 算法,相信理解起来更好,也能更好的对比。下一小节我们学习下 react 新的生命周期。

到此这篇关于react diff 算法实现思路及原理解析的文章就介绍到这了,更多相关react diff 算法内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: react diff 算法实现思路及原理解析

本文链接: https://lsjlt.com/news/149479.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • react diff 算法实现思路及原理解析
    目录事例分析diff 特点diff 思路实现 diff 算法修改入口文件实现 React.Fragment我们需要修改 children 对比前面几节我们学习了解了 react 的渲...
    99+
    2024-04-02
  • react diff算法源码解析
    目录单节点Diff reconcileSingleElement多节点Diff reconcileChildrenArray如何判断节点移动了? 总结 React中Diff算法又称为...
    99+
    2024-04-02
  • vue.js diff算法原理详细解析
    目录diff算法的概念虚拟Domh函数diff对比规则patchpatchVnodeupdateChildren总结diff算法的概念 diff算法可以看作是一种对比算法,...
    99+
    2024-04-02
  • React怎么实现核心Diff算法
    本篇内容主要讲解“React怎么实现核心Diff算法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“React怎么实现核心Diff算法”吧!Diff算法的设计思路试想,Diff算法需要考虑多少种情...
    99+
    2023-06-30
  • 浅析Vue中Virtual DOM和Diff原理及实现
    目录0. 写在开头1. vdom2. Diff0. 写在开头 本文将秉承Talk is cheap, show me the code原则,做到文字最精简,一切交由代码说明! 1. ...
    99+
    2023-03-21
    Vue Virtual DOM Diff原理 Vue Virtual DOM Vue Diff
  • 怎么在react中实现一个diff算法
    这期内容当中小编将会给大家带来有关怎么在react中实现一个diff算法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。单节点Diff单节点Diff比较简单,只有key相同并且type相同的情况才会尝试复用...
    99+
    2023-06-14
  • Vue2 的 diff 算法规则原理详解
    目录前言算法规则diff 优化策略老数组的开始与新数组的开始老数组的结尾与新数组的结尾老数组的开始与新数组的结尾老数组的结尾与新数组的开始以上四种情况都没对比成功推荐在渲染列表时为节...
    99+
    2024-04-02
  • React实现核心Diff算法的示例代码
    目录Diff算法的设计思路Demo介绍Diff算法实现遍历前的准备工作遍历after遍历后的收尾工作总结Diff算法的设计思路 试想,Diff算法需要考虑多少种情况呢?大体分三种,分...
    99+
    2024-04-02
  • 详解JavaBellman-Ford算法原理及实现
    目录一 点睛二 算法步骤三 算法实现四 测试一 点睛 如果遇到负权边,则在没有负环(回路的权值之和为负)存在时,可以采用 Bellman-Ford 算法求解最短路径。该算法...
    99+
    2024-04-02
  • React Diff算法不采用Vue的双端对比原因详解
    目录前言React 官方的解析Fiber 的结构Fiber 链表的生成React 的 Diff 算法第一轮,常见情况的比对第二轮,不常见的情况的比对重点如何协调更新位置信息小结图文解...
    99+
    2024-04-02
  • KNN算法原理及python实现
    文章目录 1 KNN算法原理1.1 基本概念1.2 KNN算法原理1.3 实现步骤1.3 KNN算法优缺点 2 python手工实现KNN算法2.1 KNN算法预测单个数据2.2 KNN算...
    99+
    2023-10-22
    python 机器学习
  • Vue的diff算法原理你真的了解吗
    目录思维导图0. 从常见问题引入1. 生成虚拟dom1. h方法实现2. render方法实现3. 再次渲染2. diff算法1. 对常见的dom做优化情况1:末尾追加一个元素(头和...
    99+
    2024-04-02
  • 详解Bagging算法的原理及Python实现
    目录一、什么是集成学习二、Bagging算法三、Bagging用于分类四、Bagging用于回归一、什么是集成学习 集成学习是一种技术框架,它本身不是一个单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习...
    99+
    2022-06-02
    Python Bagging算法 python 装袋算法
  • 详解Dijkstra算法原理及其C++实现
    目录什么是最短路径问题Dijkstra算法实现思路案例分析代码实现什么是最短路径问题 如果从图中某一顶点(称为源点)到达另一顶点(称为终点)的路径可能不止一条,如何找到一条路径使得沿...
    99+
    2024-04-02
  • Vue中Virtual DOM和Diff原理及实现方法是什么
    本篇内容介绍了“Vue中Virtual DOM和Diff原理及实现方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. v...
    99+
    2023-07-05
  • 怎么在react中实现一个虚拟dom和diff算法
    这篇文章将为大家详细讲解有关怎么在react中实现一个虚拟dom和diff算法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。虚拟DOM,见名知意,就是假的DOM,我们真实的DOM挂载在页面上...
    99+
    2023-06-14
  • react+react-beautiful-dnd实现代办事项思路详解
    目录react+react-beautiful-dnd应用效果预览实现思路index.js入口文件配置app.jsx主页面配置untils/with-context.js封装工具to...
    99+
    2024-04-02
  • Golangmap实践及实现原理解析
    目录Map实践以及实现原理使用实例内存模型创建maphash函数key定位和碰撞解决扩容元素访问删除迭代Map实践以及实现原理 使用实例内存模型创建maphash函数key定位和碰撞...
    99+
    2024-04-02
  • Java Bellman-Ford算法原理及实现方法
    本篇内容介绍了“Java Bellman-Ford算法原理及实现方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一 点睛如果遇到...
    99+
    2023-07-02
  • React Context 变迁及背后实现原理详解
    目录Context老的 Context API基础示例context 中断问题解决方案新的 Context API基础示例模拟实现createContext 源码Context 本篇...
    99+
    2022-11-13
    React Context 变迁原理 React Context
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作