react 原理篇

11 阅读6分钟

1. 虚拟dom

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

优点

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

Virtaul dom 简单实现

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工作流程

// 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() 或实现类似行为。

使用示例:

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

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

// 模拟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')
})