Skip to main content

react 面试题

react 的事件机制 ?

  1. 优点
  • 事件都绑定到了 div#root 上 可以大量节省内存占用,减少事件注册,当新增子对象时无需再次对其绑定
  • 合成事件:围绕浏览器原生事件充当跨浏览器包装器的对象,它们将不同浏览器的行为合并为一个 API,这样做是为了确保事件在不同浏览器中显示一致的属性(重写 preventDefault 和 stopPropagation)
  1. 底层实现
  • 通过插件系统注册事件名
  • div#root绑定事件
    • 遍历所有的原生的事件比如 click,进行监听
    • 生成 dispatchEvent 函数,绑定两个阶段的事件
  • 派发事件冒泡和捕获(点击事件源)
    • 获取事件源,拿到真实 DOM 和对应的 fiber
    • 从事件源向上递遍历 fiber,将对应事件映射的函数添加到 listeners
    • 创建合成事件实例,兼容处理
    • 将对应的合成事件和函数添加到 dispatchQueue
  • 区分冒泡和捕获 执行对应的回调函数

并发模式

React 的 并发模式(Concurrent Mode) 是 React 为了解决复杂应用中 UI 更新卡顿、提高用户体验而引入的一种新特性。它通过并发处理多个任务,让 React 能够更灵活、更智能地调度渲染任务,从而提升应用的响应性和性能。

并发模式的核心目标是让 React 应用可以更自然地处理复杂的用户交互场景,如大型表单的输入响应、动画渲染,以及背景数据加载等任务,而不会影响应用的流畅度。React 并发模式基于其底层的 Fiber 架构。

并发模式的核心概念

  1. 可中断渲染
    • 在传统的 React 渲染模型中,渲染任务是同步的,即一旦开始渲染任务,它会阻塞直到渲染完成。这意味着如果渲染任务较大,用户的交互可能会变得迟钝。
    • 并发模式允许 React 在执行较长时间的渲染任务时,可以中途暂停这个任务,以便先处理更高优先级的任务,比如用户输入、动画等。
  2. 任务优先级调度
    • React 并发模式通过引入任务优先级,使得渲染和更新任务可以根据重要性进行调度。高优先级的任务(如用户交互)会优先执行,而低优先级任务(如背景数据加载)则可以稍后执行或者被延迟。
    • 这种优先级调度确保了 React 应用在用户交互密集的场景下,仍然能够保持响应迅速。
  3. 时间切片(Time Slicing)
    • 并发模式利用了时间切片技术,将一个大的渲染任务分成多个小任务执行。每个时间片结束时,React 可以检查当前的任务优先级,决定是继续当前任务,还是切换到其他更高优先级的任务。
    • 这使得 React 可以在不同的任务之间切换,保持 UI 的流畅性。
  4. 暂停和恢复渲染
    • React 在并发模式下,可以暂停某个渲染任务,并在未来的某个时刻恢复它。这种灵活性使得 React 能够更好地应对复杂场景下的 UI 更新,避免长时间阻塞主线程的情况。
  5. 并行处理多个状态更新
    • 在并发模式中,React 可以并行处理多个状态更新,而不是按顺序处理。这使得 React 能够在后台准备和执行多个渲染任务,并在适当的时机提交最优结果。

并发模式的应用场景

  • 用户输入与渲染之间的平衡:并发模式允许 React 在用户输入时中断渲染任务,优先处理输入事件,避免因长时间渲染任务导致输入延迟。
  • 平滑的动画和过渡效果:React 能够优先处理动画或过渡效果,让界面保持流畅,同时后台完成其他的更新任务。
  • 复杂的背景数据加载:并发模式让 React 在加载和更新数据时,可以将部分渲染任务推迟到更合适的时机,确保不会影响用户的交互体验。

diff 算法

新的虚拟 dom 和老的 fiber 树构建新的 fiber 树

单节点

多节点

  • 开始第一轮循环 如果老 fiber 有值,新的虚拟 DOM(是多节点)也有值 ,遍历新的虚拟 dom
    • 如果 key 不同则直接结束本轮循环,
    • key 一样,比较 type,试图更新或者试图复用老的 fiber,否则创建新 fiber
    • 检查是否成功复用老 fiber,未成功服用的需要给当前 fiber 的父 fiber 添加删除的标识,将新的 fiber 标记位插入
    • 继续循环
  • 开启第二轮遍历
    • 新的虚拟 dom newChildren 遍历完而 oldFiber 还有,遍历剩下所有的 oldFiber 标记为删除,DIFF 结束
    • 老的 fiber (oldFiber) 遍历完了,而新的虚拟 dom(newChildren) 还有,将剩下的 newChildren 标记为插入,DIFF 结束
    • newChildren 和 oldFiber 都同时遍历完成,diff 结束
    • newChildren 和 oldFiber 都没有完成,则进行节点移动的逻辑
  • 移动(遍历新的虚拟 dom, 老的 fiber 放 map, 便利判断节点是否需要删除 移动 复用 新建)
    • 把剩余 oldFibers 放到 map 中, fiber 和 key 或者 index 做映射
    • 开始遍历剩下的虚拟 DOM 子节点
    • 在 map 中 查找该虚拟 dom 节点是否有可用的 fiber 能复用,不能就新建 fiber 并返回
    • 如果在 map 中找到 说明复用成功,则 map 中删除复用的该 fiber
    • 指定新的 fiber 存放位置 ,判断是否需要移动
    • 定义一个 lastPlacedIndex 默认为 0
    • 如果没有老 fibe 直接标记 flag 为 Placement
    • 如果有老 fiber,比较老 fiber 中的索引 oldIndex 和 lastPlacedIndex,
      • 如果 oldIndex< lastPlacedIndex,标记该节点需要移动,并且把 oldIndex 赋值给 lastPlacedIndex
      • 否则说明该节点不需要以移动,无须添加副作用
    • 遍历下一个节点
    • 等全部处理完后,删除 map 中所有剩下的老 fiber
    • commit 阶段处理副作用 更新 删除 或者添加