深入浅出:手把手实现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>
  )
}
相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic7 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g11 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森11 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http