美文网首页
五、update children

五、update children

作者: sweetBoy_9126 | 来源:发表于2024-03-29 17:00 被阅读0次

更新 -> 创建和删除

  1. type 不一致
    删除旧的 创建新的
  • demo
import React from './core/React.js'
let showBar = false
function Counter() {
  const foo = <div>foo</div>
  const bar = <p>bar</p>

  function handleShowBar() {
    showBar = !showBar 
    React.update()
  }

  return (
    <div>
      Counter
      <div>{showBar ? bar : foo}</div>
      <button onClick={handleShowBar}>showBar</button>
    </div>
  )
}
function App() {
  return (
    <div>
  hi-mini-react
  <Counter></Counter>
    </div>
  )
}
export default App

上面我们每次点击 showBar 的时候希望 bar 和 foo 来回切换,但是现在代码每次点击都会添加新的不会把之前的旧的删除

方法:
把我们需要删除的节点存到一个数组里,然后在 commitRoot 的时候统一处理

+ let deletions = []
const initChildren = (fiber, children) => {
    children.forEach((child, index) => {
      const isSameType = oldFiber && oldFiber.type === child.type
      let newFiber
      if (isSameType) {} else {
        newFiber = {
        type: child.type,
        props: child.props,
        child: null, // child 和 sibling 初始化我们不知道
        sibling: null,
        parent: fiber,
        dom: null,
        effectTag: 'placement'
      }
+        if (oldFiber) {
+          deletions.push(oldFiber)
+        }
      }
}
+ const commitDeletion = (fiber) => {
+   fiber.parent.dom.removeChild(fiber.dom)
+ }
const commitRoot = () => {
  // 这里为啥不是root.props.children
+  deletions.forEach(commitDeletion)
  ...
+  deletions = []
}

问题:如果我们的节点里面有函数组件就会报错,因为 FC 没有 dom,所以我们应该删除它的 child,也就是递归调用 commitDeletion 但是 child 的父级是 FC 依然没有dom 所以要继续找它父级的父级是否有dom

const commitDeletion = (fiber) => {
  if (fiber.dom) {
    let fiberParent = fiber.parent
    while(!fiberParent.dom) {
      fiberParent = fiberParent.parent
    }
    fiberParent.dom.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child) 
  }
}
  1. 新的比老的短
    多出来的节点需要删除掉
  • demo
import React from './core/React.js'
let showBar = false
function Counter() {
  const foo = (
    <div>
      foo
      <div>child</div>
    </div>
  )
  const bar = <p>bar</p>

  function handleShowBar() {
    showBar = !showBar 
    React.update()
  }

  return (
    <div>
      Counter
      <button onClick={handleShowBar}>showBar</button>
      <div>{showBar ? bar : foo}</div>
    </div>
  )
}
function App() {
  return (
    <div>
  hi-mini-react
  <Counter></Counter>
    </div>
  )
}
export default App

因为我们处理的时候是根据新节点的 children 来遍历的

  children.forEach((child, index) => {
    // 开始对比
    const isSameType = oldFiber && oldFiber.type === child.type
    let newFiber
    if (isSameType) {
      // update
      newFiber = {
        type: child.type,
        props: child.props,
        child: null, // child 和 sibling 初始化我们不知道
        sibling: null,
        parent: fiber,
        // 更新不会新创建 dom
        dom: oldFiber.dom,
        alternate: oldFiber,
        effectTag: 'update'
      }
    } else {
      // 添加
      newFiber = {
        type: child.type,
        props: child.props,
        child: null, // child 和 sibling 初始化我们不知道
        sibling: null,
        parent: fiber,
        dom: null,
        effectTag: 'placement'
      }
      if (oldFiber) {
        deletions.push(oldFiber)
      }
    }
    if (oldFiber) {
      // 多个子级
      oldFiber = oldFiber.sibling
    }
    if (index === 0) {
      fiber.child = newFiber
    } else {
      // 如果不是第一个孩子就需要绑定到sibling,也就是上一个孩子的sibling 上,所以我们需要知道上一个孩子
      prevChild.sibling = newFiber
    }
    // 考虑到我们还需要设置 parent.sibling,因为我们是从上往下获取的,所以work肯定是顶层也就是 parent,我们只能给 child 设置,
    // 但是如果直接在child 上加就会破坏原有结构,所以我们单独维护一个newWork 对象,
    prevChild = newFiber
  })
  console.log(oldFiber)

我们最后一次遍历的是遍历的心节点的倒数第一个div,它的children 里的的child 是bar,我们的 oldFiber 指向的是之前的也就是 foo,遍历完成 oldFiber 被赋值成了一个新的兄弟节点也就是老的下面的倒数第一个 div

  if (oldFiber) {
    deletions.push(oldFiber)
  }

问题:如果我们有多个自己节点

  const foo = (
    <div>
      foo
      <div>child</div>
      <div>child2</div>
    </div>
  )

我们除了要删除它,还要删除它的兄弟节点

  while (oldFiber) {
    deletions.push(oldFiber)
    oldFiber = oldFiber.sibling
  }
  1. edge case
  • demo
import React from './core/React.js'
let showBar = false
function Counter() {
  const bar = <p>bar</p>

  function handleShowBar() {
    showBar = !showBar 
    React.update()
  }

  return (
    <div>
      Counter
      <button onClick={handleShowBar}>showBar</button>
      <div>{showBar && bar}</div>
    </div>
  )
}
function App() {
  return (
    <div>
  hi-mini-react
  <Counter></Counter>
    </div>
  )
}
export default App

解决方式:判断如果是 false 就不处理

const initChildren = (fiber, children) => {
  if (isSameType) {

  } else {
    + if (child) {
        newFiber = {
          type: child.type,
          props: child.props,
          child: null, // child 和 sibling 初始化我们不知道
          sibling: null,
          parent: fiber,
          dom: null,
          effectTag: 'placement'
        }
      }
  }
  • demo
    <div>
      Counter
      <div>{showBar && bar}</div>
      <button onClick={handleShowBar}>showBar</button>
    </div>
image.png

问题:因为我们第二个节点是 false,我们如果是false的时候不会赋值 newFiber 所以 pervChild = newFiber 就是 null

    if (newFiber) {
      prevChild = newFiber
    }
  1. 优化更新
    问题:更新子组件的时候,其他不相关的组件也会重新执行,造成了浪费
import React from './core/React.js'
let countFoo = 1
function Foo() {
  console.log('foo return')
  function handleClick() {
    countFoo++
    React.update()
  }
  return (
    <div>
      <h1>foo</h1>
      {countFoo}
      <button onClick={handleClick}>click</button>
    </div>
  )
}
let countBar = 1
function Bar() {
  console.log('bar return')
  function handleClick() {
    countBar++
    React.update()
  }
  return (
    <div>
      <h1>bar</h1>
      {countBar}
      <button onClick={handleClick}>click</button>
    </div>
  )
}
let countRoot = 1
function App() {
  console.log('app return')
  function handleClick() {
    countRoot++
    React.update()
  }
  return (
    <div>
      hi-mini-react: {countRoot}
      <button onClick={handleClick}>click</button>
      <Foo></Foo>
      <Bar></Bar>
    </div>
  )
}
export default App

每次 update 的时候 三个组件都会重新 render



我们之前的更新逻辑是更新的时候重新指定根节点,从根节点一步步的执行,直到把整棵树执行完成
改进:只需要更新我们操作的那个组件的树,比如 Foo组件和它下面的节点,所以我们需要知道当前组件的开始节点和结束节点
开始节点:Foo
结束节点:当处理到兄弟节点的时候也就是Bar 的时候
获取开始节点:
在 updateFunctionComponent 中

const updateFunctionComponent = (fiber) => {
  wipFiber = fiber
}
const update = () => {
    console.log(wipFiber, 'www')
}

点击 Foo 但是我们会发现打印的却是 Bar,因为 wiperFiber 每次会被覆盖
解决方法:使用闭包

const update = () => {
  let currentFiber = wipFiber
  return () => {
    console.log(currentFiber)
    nextWorkOfUnit = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot
    }
    wipRoot = nextWorkOfUnit
  }
}

改造 demo

function Foo() {
  console.log('foo return')
  const update = React.update()
  function handleClick() {
    countFoo++
    update()
  }
  return (
    <div>
      <h1>foo</h1>
      {countFoo}
      <button onClick={handleClick}>click</button>
    </div>
  )
}
const update = () => {
  let currentFiber = wipFiber
  return () => {
    wipRoot = {
      ...currentFiber,
      alert: currentFiber
    }
    // wipRoot = {
    //   dom: currentRoot.dom,
    //   props: currentRoot.props,
    //   alternate: currentRoot
    // }
    nextWorkOfUnit = wipRoot
  }
}

处理结束点
处理时机在赋值下一个节点的时候

 nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
    if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
      nextWorkOfUnit = null
    }

https://github.com/wanglifa/mini-react/commit/3cb79f8d640207023b67f8a063553f56c2519dff

相关文章

网友评论

      本文标题:五、update children

      本文链接:https://www.haomeiwen.com/subject/bxcptjtx.html