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

相关推荐
华玥作者4 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_5 小时前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠5 小时前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
lang201509285 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC6 小时前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务7 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
嘿起屁儿整7 小时前
面试点(网络层面)
前端·网络
VT.馒头7 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript
phltxy8 小时前
Vue 核心特性实战指南:指令、样式绑定、计算属性与侦听器
前端·javascript·vue.js
Byron07079 小时前
Vue 中使用 Tiptap 富文本编辑器的完整指南
前端·javascript·vue.js