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

相关推荐
ℋᙚᵐⁱᒻᵉ鲸落3 小时前
移动端滑动手势冲突:overflow: auto导致外层横向滑动失效
前端·javascript·css·vue.js·html
默_笙4 小时前
🎃 前端学了 Next.js,后端该学啥?NestJS 就是 Node 版的蜜雪冰城
前端·javascript
前端snow5 小时前
ai agent --- 实现 openclaw 定时间效果
前端
码云之上6 小时前
换模型之后,Chatbot 为什么要自己做 compact?
前端·agent·前端工程化
星栈6 小时前
自定义 UI-UX-Pro-Max 的 CSV 知识库,把 AI 生成的后台拉回行业该有的样子
前端·agent·weui
研☆香6 小时前
简单的图片上传 删除 预览
前端
ITresearchGuest6 小时前
我用 AI 一小时写了一个世界杯数据可视化平台|前端 VibeCoding 初体验
前端·人工智能·信息可视化
慧一居士8 小时前
Vite项目中使用Less步骤,详细使用示例
前端·css
花间相见8 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain
kyriewen8 小时前
面试官让我用 AI 重构一个 8 年陈的 React 组件——他说他不看代码,只看我会不会拆
前端·javascript·面试