深入浅出:手把手实现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>
  )
}
相关推荐
怕浪猫6 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
西瓜太郎12346 小时前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
GreenTea6 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
几何心凉9 小时前
没有万能模型:我用 AiiOnly Token Plan,把多款大模型的长处拼进同一个项目
前端
kyriewen9 小时前
Claude Code 的额度今天缩水了17%——官方公告上写的是"永久提高25%"
前端·ai编程·claude
雪芽蓝域zzs10 小时前
vue解构平铺VS对象包裹
前端·javascript·vue.js
风骏时光牛马11 小时前
从零到实战,完整AI学习路线
前端
三十而立洋11 小时前
深入理解 Monorepo:子包安装依赖
前端·前端工程化
IT_陈寒11 小时前
Java Stream处理大集合,我的内存怎么就炸了
前端·人工智能·后端
Captaincc12 小时前
AI 用量桌面端-桌面宠物自定义指南
前端·人工智能