深入浅出:手把手实现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>
  )
}
相关推荐
LFly_ice11 分钟前
学习React-10-useTransition
前端·学习·react.js
咔咔一顿操作13 分钟前
【CSS 3D 交互】实现精美翻牌效果:从原理到实战
前端·css·3d·交互·css3
知识分享小能手16 分钟前
React学习教程,从入门到精通,React 构造函数(Constructor)完整语法知识点与案例详解(16)
前端·javascript·学习·react.js·架构·前端框架·vue
召摇22 分钟前
Nue.js深度解析:极简主义前端框架的革新实践
前端·node.js
小徐_233325 分钟前
uni-app 也能使用 App.vue?wot-starter 是这样实现的!
前端·uni-app
入秋26 分钟前
Three.js后期处理实战:镜头颜色、色差、点阵与颜色管道的深度解析
前端·three.js
深圳外环高速27 分钟前
企业微信和页面离开事件
前端
召摇29 分钟前
NodeBB 深度解析:现代论坛系统的架构设计与实践指南
前端·javascript
哆啦A梦158842 分钟前
uniapp分包实现
前端·vue.js·uni-app·vue3
wordbaby1 小时前
Hooks的革命:让React的非UI逻辑也能像UI组件一样自由复用和组合
前端·react.js