深入浅出:手把手实现Mini-React中的Props更新机制

深入浅出:手把手实现Mini-React中的Props更新机制

在实现React-like框架时,props的高效更新是核心挑战之一。本文将带您逐步实现Mini-React中的props更新机制,通过菜市场摊位更新的生动比喻,让复杂概念变得通俗易懂。


一、获取新的DOM树

更新时需要获取新的DOM树结构,类似初始化时的render过程。我们通过currentRoot记录当前根节点,更新时将其作为起点:

tsx 复制代码
let currentRoot = null
​
function commitRoot() {
  commitWork(root.child)
  currentRoot = root  // 更新后记录为当前根节点
  root = null
}
​
function update() {
  nextWorkOfUnit = {
    dom: currentRoot.dom,
    props: currentRoot.props,
    alternate: currentRoot  // 关键:建立新旧节点联系
  }
  root = nextWorkOfUnit
}

二、建立新旧节点对比

通过alternate指针连接新旧节点,实现差异更新。就像菜市场老板对比新旧进货单:

tsx 复制代码
function initChildren(fiber, children) {
  let oldFiber = fiber.alternate?.child  // 昨天的进货单
  let prevChild = null
  
  children.forEach((child, index) => {
    const isSameType = oldFiber?.type === child.type
    
    const newFiber = isSameType 
      ? {  // 同类型蔬菜:更新价格牌
          type: child.type,
          props: child.props,
          dom: oldFiber.dom,
          effectTag: "update",
          alternate: oldFiber
        }
      : {  // 新品种蔬菜:准备新位置牌
          type: child.type,
          props: child.props,
          dom: null,
          effectTag: "placement"
        }
    
    if (oldFiber) oldFiber = oldFiber.sibling  // 查看下个旧节点
    
    // 构建链表关系
    if (index === 0) fiber.child = newFiber
    else prevChild.sibling = newFiber
    
    prevChild = newFiber
  })
}

三、提交阶段执行DOM操作

在commit阶段根据effectTag执行具体操作,类似老板布置摊位:

tsx 复制代码
function commitWork(fiber) {
  if (!fiber) return
  
  // 向上找到真实DOM父节点(跳过函数组件)
  let parentFiber = fiber.parent
  while (!parentFiber.dom) {
    parentFiber = parentFiber.parent
  }
  
  // 根据标签执行操作
  if (fiber.effectTag === "update") {
    updateProps(fiber.dom, fiber.props, fiber.alternate?.props)
  } else if (fiber.effectTag === "placement" && fiber.dom) {
    parentFiber.dom.append(fiber.dom)  // 挂新位置牌
  }
  
  // 深度优先遍历子节点和兄弟节点
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

四、智能属性更新

属性更新需要特殊处理事件监听器,避免重复绑定:

tsx 复制代码
function updateProps(dom, nextProps, prevProps = {}) {
  // 移除旧属性(昨天有今天没有)
  Object.keys(prevProps).forEach(key => {
    if (key !== "children" && !(key in nextProps)) {
      dom.removeAttribute(key)
    }
  })
  
  // 设置新属性(新增或修改)
  Object.keys(nextProps).forEach(key => {
    if (key === "children") return
    if (nextProps[key] !== prevProps[key]) {
      if (key.startsWith("on")) {  // 事件处理
        const eventType = key.slice(2).toLowerCase()
        dom.removeEventListener(eventType, prevProps[key]) // 关键!
        dom.addEventListener(eventType, nextProps[key])
      } else {
        dom[key] = nextProps[key]  // 普通属性
      }
    }
  })
}

五、手动更新测试

通过手动触发更新验证机制:

tsx 复制代码
function Counter() {
  const [count, setCount] = useState(10)
  
  function handleClick() {
    setCount(count + 1)  // 实际应触发更新
    React.update()       // 手动更新演示
  }
  
  return (
    <div>
      Count: {count}
      <button onClick={handleClick}>+1</button>
    </div>
  )
}
相关推荐
小小小小宇2 小时前
虚拟列表兼容老DOM操作
前端
悦悦子a啊2 小时前
Python之--基本知识
开发语言·前端·python
安全系统学习3 小时前
系统安全之大模型案例分析
前端·安全·web安全·网络安全·xss
涛哥码咖4 小时前
chrome安装AXURE插件后无效
前端·chrome·axure
OEC小胖胖4 小时前
告别 undefined is not a function:TypeScript 前端开发优势与实践指南
前端·javascript·typescript·web
行云&流水4 小时前
Vue3 Lifecycle Hooks
前端·javascript·vue.js
Sally璐璐4 小时前
零基础学HTML和CSS:网页设计入门
前端·css
老虎06274 小时前
JavaWeb(苍穹外卖)--学习笔记04(前端:HTML,CSS,JavaScript)
前端·javascript·css·笔记·学习·html
灿灿121385 小时前
CSS 文字浮雕效果:巧用 text-shadow 实现 3D 立体文字
前端·css
烛阴5 小时前
Babel 完全上手指南:从零开始解锁现代 JavaScript 开发的超能力!
前端·javascript