mini-react 实现组件更新与删除机制详解

mini-react 实现组件更新与删除机制详解:type 不一致即替换

在构建 mini-react 的过程中,组件的 更新与删除机制 是核心功能之一。特别是在虚拟 DOM diff 过程中,当新旧节点的 type 不一致时,应当删除旧节点并创建新节点。本文将通过示例和源码解析,带你实现一个基础的节点替换逻辑,包括对函数组件的特殊处理。


🎯 场景说明:div 替换为 p

我们通过一个切换组件的小例子来演示替换的过程。初始展示 div,点击按钮后变为 p 标签。

测试代码:App.jsx

tsx 复制代码
import React from './core/React.js';
​
let showBar = false
​
function Counter({ num }) {
  const Foo = <div>foo</div>
  const Bar = <p>bar</p>
​
  function handleShowBar() {
    showBar = !showBar
    React.update()
  }
​
  return <div>
    Counter
    <div>{showBar ? Bar : Foo}</div>
    <button onClick={handleShowBar}>show bar</button>
  </div>
}
​
function App() {
  return <div>
    hi-mini-react
    <Counter></Counter>
  </div>
}
export default App;

运行上述代码时发现:点击按钮后,并未删除旧节点,仅简单添加了新节点。


🧩 删除策略:收集后统一删除

我们需要在 initChildren 中识别出类型不一致的旧节点,并将其添加到 deletions 数组中,在 commitRoot 中统一删除。

修改 initChildren

tsx 复制代码
function initChildren(fiber, children) {
  children.forEach((child, index) => {
    const isSameType = oldFiber && oldFiber.type === child.type
    let newFiber
​
    if (isSameType) {
      newFiber = {
        type: child.type,
        props: child.props,
        child: null,
        parent: fiber,
        sibling: null,
        dom: oldFiber.dom,
        effectTag: "update",
        alternate: oldFiber
      }
    } else {
      newFiber = {
        type: child.type,
        props: child.props,
        child: null,
        parent: fiber,
        sibling: null,
        dom: null,
        effectTag: "placement"
      }
      if (oldFiber) {
        console.log('oldFiberShouldDelete', oldFiber)
        deletions.push(oldFiber)
      }
    }
  })
}

🧹 commit 阶段统一删除

commitRoot 中统一执行删除操作:

tsx 复制代码
function commitRoot() {
  deletions.forEach(commitDeletion)
  commitWork(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
  deletions = []
}
​
function commitDeletion(fiber) {
  fiber.parent.dom.removeChild(fiber.dom)
}

此时效果正常,div 能被成功替换为 p


⚠️ 处理函数组件:fiber.dom 为 null 问题

FooBar 改为函数组件:

tsx 复制代码
function Counter({ num }) {
  const Foo = () => <div>foo</div>
  const Bar = () => <p>bar</p>
​
  function handleShowBar() {
    showBar = !showBar
    React.update()
  }
​
  return <div>
    Counter
    <div>{showBar ? <Bar /> : <Foo />}</div>
    <button onClick={handleShowBar}>show bar</button>
  </div>
}

点击按钮时报错:fiber.dom is null。这是因为函数组件本身没有 DOM 节点,因此无法直接删除。

修复方案:递归向下查找有 dom 的子节点

tsx 复制代码
function commitDeletion(fiber) {
  let fiberParent = fiber.parent
  while (!fiberParent.dom) {
    fiberParent = fiberParent.parent
  }
​
  if (fiber.dom) {
    fiberParent.dom.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child)
  }
}

此修改后,函数组件下的真实 DOM 也能被正确移除。


✅ 总结(100字)

本文讲解了在 mini-react 中实现组件更新和删除的过程。通过比较旧新节点的 type 实现替换策略,借助 deletions 队列实现统一删除,并对函数组件做了特殊处理,确保无 DOM 的节点也能正确卸载,完整实现一个基础但关键的 diff 更新逻辑。

相关推荐
vvilkim10 分钟前
Flutter 常用组件详解:Text、Button、Image、ListView 和 GridView
前端·flutter
vvilkim16 分钟前
Flutter 命名路由与参数传递完全指南
前端·flutter
NA17 分钟前
redis
前端
你真好看_17 分钟前
6年低代码 零代码 系统二开人员的角度,看低代码 到底有多好用!!!
前端
JC_You_Know25 分钟前
边缘计算一:现代前端架构演进图谱 —— 从 SPA 到边缘渲染
前端·人工智能·边缘计算
DoraBigHead27 分钟前
深入 JavaScript 作用域机制:透视 V8 引擎背后的执行秘密
前端·javascript
Tu_Jipang29 分钟前
前端从零搭建企业级后台系统实战指南
前端
快起来别睡了30 分钟前
CSS定位的奥秘:从文档流到position,一文讲透前端布局核心!
前端·css·程序员
菥菥爱嘻嘻33 分钟前
React---Hooks深入
前端·javascript·react.js
只与明月听35 分钟前
前端学算法-二叉树(一)
前端·javascript·算法