深入浅出:手把手实现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>
  )
}
相关推荐
Hilaku1 分钟前
为什么我放弃使用 Pinia?
前端·javascript·vue.js
yrjw5 分钟前
使用ReactNative加载Svga动画支持三端【android/ios/harmony】
前端
贩卖纯净水.6 分钟前
webpack继续学习
前端·学习·webpack
陈_杨7 分钟前
鸿蒙5开发宝藏案例分享---在线短视频流畅切换
前端·javascript
Smile_frank10 分钟前
vue-06(“$emit”和事件修饰符)
前端
前端小巷子16 分钟前
JS的 DOM 尺寸与位置属性
前端·javascript·面试
二月垂耳兔79827 分钟前
Vue3基础
前端
smallluan35 分钟前
入门AJAX——XMLHttpRequest(Post)
前端·ajax·okhttp
百锦再40 分钟前
# Vue + OpenLayers 完整项目开发指南
开发语言·前端·javascript·vue.js·python·ecmascript·tkinter
Jinxiansen021143 分钟前
Vue + Element Plus 实战:大文件切片上传 + 断点续传
前端·javascript·vue.js