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 更新逻辑。

相关推荐
技术小丁8 分钟前
uni-app 广告弹窗最佳实践:不扰民、可控制频次、含完整源码
前端·uni-app·1024程序员节
quan26319 分钟前
日常开发20251022,传统HTML表格实现图片+视频+预览
前端·javascript·html·html列表实现图片+视频
陶甜也35 分钟前
ThreeJS曲线动画:打造炫酷3D路径运动
前端·vue·threejs
楊无好39 分钟前
react中的受控组件与非受控组件
前端·react.js
菠萝+冰41 分钟前
react虚拟滚动
前端·javascript·react.js
落一落,掉一掉1 小时前
第十三周前端加密绕过
前端
高德开放平台2 小时前
实战案例|借助高德开放平台实现智慧位置服务:路线导航的开发与实践
前端·后端
老前端的功夫2 小时前
# HTTP缓存:从懵懵懂懂到了如指掌
前端
安卓开发者2 小时前
Docker与Nginx:现代Web部署的完美二重奏
前端·nginx·docker
Dorian_Ov02 小时前
GeoPandas+DataFrame实现shapefile文件导入PostGIS数据库
前端·gis