react 原理篇

1. 虚拟dom

Virtual Dom 是指用javascript去描述一个DOM结构,虚拟DOM不是直接去操作浏览器DOM,而是在虚拟DOM中对UI进行更新,减少不必要的真实DOM操作。

优点

  1. 性能优化:减少不必要的真实DOM操作,节省性能开销。(主要体现在diff算法等)
  2. 跨平台性:虚拟DOM是不受限平台的,不同平台可以做不同的映射目标。比如:虚拟DOM可以映射出微信小程序,electron,react , react native 多套UI。

Virtaul dom 简单实现

js 复制代码
const React = {
  createElement (type, props, ...children) {
    return {
      type,
      props: {
        ...props,
        children: children.map(child => {
          if (typeof child === 'object') {
            return child
          } else {
            return React.createTextElement(child)
          }
        })
      }
    }
  },
  createTextElement (text) {
    return {
      type: 'TEXT_ELEMENT',
      props: {
        nodeValue: text,
        children: []
      }
    }
  }
}

const vdom = React.createElement('div', { title: 'KKK' }, React.createElement('H1', {}, 'nihao'))

console.log(JSON.stringify(vdom, null, 2))
/*
{
  "type": "div",
  "props": {
    "title": "KKK",
    "children": [
      {
        "type": "H1",
        "props": {
          "children": [
            {
              "type": "TEXT_ELEMENT",
              "props": {
                "nodeValue": "nihao",
                "children": []
              }
            }
          ]
        }
      }
    ]
  }
}
*/

2. Fiber 架构

React Fiber 是 React 16 中引入的新的核心协调(reconciliation)引擎

"Fiber"是对一个组件实例或 DOM 节点的抽象表示。每个 Fiber 对象代表了某个组件或元素,并保存了与之相关的状态、属性、子节点等信息

  • 每个 React 组件都会对应一个 Fiber。
  • Fiber 构成了一个树状结构(Fiber Tree),用于描述整个 UI 的层级关系。

Fiber 的能力/目标:

  1. ✅ 支持异步渲染(Concurrent Mode)
  2. ✅ 实现任务可中断/恢复
  3. ✅ 支持不同更新的优先级处理
  4. ✅ 支持双缓存树(Fiber Tree):Fiber 架构中有两棵Fiber树 current fiber tree(当前正在渲染的 Fiber树)work in progress fiber tree(正在处理的 Fiber 树)。React 使用这两棵树来保存更新前后的状态,从而更高效地进行比较和更新
  5. ✅ 支持任务切片:在浏览器的空闲时间内(利用 requestldleCallback思想),React 可以将渲染任务拆分成多个小片段,逐步完成 Fiber 树的构建,避免一次性完成所有渲染任务导致的阻塞。

Fiber 的工作原理

1. 虚拟 DOM 到 Fiber 树的映射

  • 在旧版 React 中,使用的是"递归渲染",即从根组件开始逐层向下渲染,无法中断。
  • Fiber 引入了一个可中断的渲染机制,将渲染过程拆分成多个小任务,由调度器来决定执行顺序。

2. Reconciliation(协调)阶段:Diffing 算法的升级

Fiber 使用了改进的 Diffing 算法,将更新操作分为两个阶段:

  1. 👉 阶段一:Render Phase(构建 Fiber 树)

    • React 创建一个新的 Fiber 树(称为 workInProgress 树),基于当前的 JSX 和 props。
    • 这个阶段可以被中断,以便优先处理更高优先级的任务(如用户输入)。
  2. 👉 阶段二:Commit Phase(提交到真实 DOM)

    • 当 Render 阶段完成后,React 将差异一次性更新到真实 DOM。
    • 此阶段不可中断,必须完整执行。

3. 任务调度与优先级管理

  1. Fiber 引入了任务优先级的概念,例如:
    • 用户输入 > 动画 > 数据更新
    • 可以通过 ReactDOM.flushSync() 手动提升某些更新的优先级
  2. React 使用内部的 Scheduler(调度器) 来决定哪些任务应该先执行,哪些可以延迟。

3. 虚拟dom + fiber初始化,简单实现react工作流程

js 复制代码
// virtaul dom
const React = {
  // type 标签类型,props 标签包含属性 children 是子元素
  createElement (type, props, ...children) {
    return {
      type,
      props: {
        ...props,
        children: children.map(child => {
          if (typeof child === 'object') {
            return child
          } else {
            return React.createTextElement(child)
          }
        })
      }
    }
  },
  createTextElement (text) {
    return {
      type: 'TEXT_ELEMENT',
      props: {
        nodeValue: text,
        children: []
      }
    }
  }
}

// 工作单元fiber初始化
let nextUnitWork = null // 下一个工作单元
let wipRoot = null  // 工作中的根节点,fiber树
let currentRoot = null // 当前fiber树(旧的fiber树,因为马上要替换)
let deletions = null // 要删除的fiber节点单元

function render (element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    },
    alternate: currentRoot //关联旧的fiber树
  }
  deletions = []
  nextUnitWork = wipRoot
}

// 工作单元fiber执行
function workloop (deadline) {
  let shouldYield = false // 是否有空闲时间
  // 有空闲时间  且  有任务
  while (nextUnitWork && !shouldYield) {
    nextUnitWork = performUnitWork(nextUnitWork)
    shouldYield = deadline.timeRemaining() < 1
  }
  if (!nextUnitWork && wipRoot) {
    commitRoot()
  }
  requestIdleCallback(workloop)
}
requestIdleCallback(workloop)


// 创建fiber节点
const createFiber = (element, parent) => {
  return {
    type: element.type,
    props: element.props,
    parent: parent,
    dom: null,
    alternate: null,
    effectTag: null,
    sibling: null,
    child: null
  }
}

// 构建子节点fiber树,遍历子节点和兄弟节点,diff算法实现
const reconcileChildren = (fiber, elements) => {
  // 生成fiber three
  let index = 0
  let prevSibling = null // 上一个兄弟节点
  // diff 算法
  let oldFiber = fiber.alternate && fiber.alternate.child
  while (index < elements.length || oldFiber !== null) {
    const element = elements[index]
    let newFiber = null
    // 复用节点
    const someFiber = oldFiber && element && element.type === oldFiber.type
    if (someFiber) {
      console.log('复用节点', element)
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        parent: fiber,
        dom: oldFiber.dom,
        alternate: oldFiber,
        effectTag: 'UPDATE' // 更新
      }
    }
    // 新增节点
    if (element && !someFiber) {
      console.log('新增节点', element)
      newFiber = createFiber(element, fiber)
      newFiber.effectTag = 'PLACEMENT' // 新增
    }
    // 删除节点
    if (oldFiber && !someFiber) {
      console.log('删除节点', oldFiber)
      oldFiber.effectTag = "DELETION"
      deletions.push(oldFiber)
    }
    if (oldFiber) {
      oldFiber = oldFiber.sibling
    }
    if (index === 0) {
      fiber.child = newFiber
    } else if (element) {
      prevSibling.sibling = newFiber
    }
    prevSibling = newFiber
    index++
  }
  return null
}


function creatDom (fiber) {
  const dom = fiber.type === 'TEXT_ELEMENT' ?
    document.createTextNode('') :
    document.createElement(fiber.type)
  updateDom(dom, {}, fiber.props)
  return dom
}
const updateDom = (dom, prevProps, nextProps) => {
  // 删除旧属性
  Object.keys(prevProps).filter(key => key !== 'children').forEach(key => {
    dom[key] = ''
  })
  // 添加新属性
  Object.keys(nextProps).filter(key => key !== 'children').forEach(key => {
    dom[key] = nextProps[key]
  })
}

// 执行工作单元
const performUnitWork = (fiber) => {
  if (!fiber.dom) {
    fiber.dom = creatDom(fiber)
  }
  const elements = fiber.props.children
  reconcileChildren(fiber, elements)
  // 遍历子节点
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    // 如果有兄弟节点就返回兄弟节点
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    // 如果没有兄弟节点就返回上级节点
    nextFiber = nextFiber.parent
  }
  return null
}


const commitRoot = () => {
  deletions.forEach(commitWork)
  commitWork(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
}

const commitWork = (fiber) => {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom)
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props
    )
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom)
  }
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}


const vdom = React.createElement("div", { id: "1" }, React.createElement("span", null, "我靠"))
const vdom2 = React.createElement("div", { id: "2" }, React.createElement("H1", null, "你妹"))
render(vdom, document.getElementById('root'))
setTimeout(() => {
  render(vdom2, document.getElementById('root'))
}, 2000)

9. 消息通讯 MessageChannel

MessageChannel 是一种高效的跨上下文通信机制,利用两个端口进行消息传递。

它本质上是一个宏任务,但由于执行延迟极低,常被用来模拟微任务行为

常用于:

  • Worker 与主线程之间的通信
  • iframe 之间的通信
  • 实现高性能、异步任务调度(如自定义微任务队列)
  • 替代 requestIdleCallback() 或实现类似行为。

使用示例:

js 复制代码
const channel = new MessageChannel()
channel.port1.onmessage = (res) => {
  console.log('channel.port1.onmessage : ', res.data)
}
setTimeout(() => {
  channel.port2.postMessage('我发送了一个消息')
}, 2000);

10. 模拟React调度器(基于MessageChannel)

js 复制代码
// 模拟react调度器,使用 MessageChannel 模拟 requestIdleCallback 来完成

const ImmediatePriority = 1         // 立即执行的优先级,优先级最高,如:点击事件
const UserBlockingPriority = 2      // 用户阻塞级别的优先级,如:滚动,校验
const NormalPriority = 3            // 一般优先级,如:render动画,异步请求
const LowPriority = 4               // 低优先级,如:数据埋点上报
const Idelpriority = 5              // 极低优先级,如:console

function getCurrentTime () {
  return performance.now()
}

class SimpleScheduler {
  constructor() {
    this.taskQueue = []             // 任务队列
    this.isPerformingTask = false   // 是否在执行任务
    const channel = new MessageChannel()
    this.port = channel.port2
    channel.port1.onmessage = this.performTaskUnitDeadline.bind(this)
  }
  schedulerCallback (priority, callback) {
    var time = getCurrentTime()
    this.port.postMessage('触发任务')           // 触发任务
    let timeout = null
    switch (priority) {
      case ImmediatePriority:
        timeout = -1
        break
      case UserBlockingPriority:
        timeout = 250
        break
      case NormalPriority:
        timeout = 5000
        break
      case LowPriority:
        timeout = 10000
        break
      case Idelpriority:
        timeout = 13132131
        break
      default:
        timeout = 5000
        break
    }
    const newTask = {
      callback,
      priority,
      expirationTime: time + timeout
    }
    this.push(this.taskQueue, newTask)
    if (!this.isPerformingTask) {
      this.isPerformingTask = true
      this.port.postMessage(null)
    }
  }
  performTaskUnitDeadline () {
    this.isPerformingTask = true
    this.teskloop()
    this.isPerformingTask = false
  }
  teskloop () {
    while (this.taskQueue.length > 0) {
      const currentTask = this.peak(this.taskQueue)
      if (currentTask) {
        const cb = currentTask.callback
        cb && cb()
        this.pop(this.taskQueue)
      } else {
        break
      }
    }
  }
  push (queue, task) {
    queue.push(task)
    // 排序
    queue.sort((a, b) => a.expirationTime - b.expirationTime)
  }
  peak (queue) {
    return queue[0] || null
  }
  pop (queue) {
    queue.shift()
  }
}


const s = new SimpleScheduler()

s.schedulerCallback(UserBlockingPriority, () => {
  console.log('2')
})

s.schedulerCallback(ImmediatePriority, () => {
  console.log('1')
})
相关推荐
GISer_Jing29 分钟前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪1 小时前
CSS复习
前端·css
咖啡の猫3 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲6 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5816 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路6 小时前
GeoTools 读取影像元数据
前端
ssshooter7 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
Jerry8 小时前
Jetpack Compose 中的状态
前端
dae bal8 小时前
关于RSA和AES加密
前端·vue.js
柳杉8 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化