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

相关推荐
喝拿铁写前端5 小时前
别再让 AI 直接写页面了:一种更稳的中后台开发方式
前端·人工智能
A向前奔跑6 小时前
前端实现实现视频播放的方案和面试问题
前端·音视频
十一.3666 小时前
131-133 定时器的应用
前端·javascript·html
xhxxx7 小时前
你的 AI 为什么总答非所问?缺的不是智商,是“记忆系统”
前端·langchain·llm
3824278278 小时前
python:输出JSON
前端·python·json
2503_928411568 小时前
12.22 wxml语法
开发语言·前端·javascript
光影少年8 小时前
Vue2 Diff和Vue 3 Diff实现及底层原理
前端·javascript·vue.js
傻啦嘿哟8 小时前
隧道代理“请求监控”实战:动态调整采集策略的完整指南
前端·javascript·vue.js
JIngJaneIL8 小时前
基于java + vue个人博客系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot