五、update children

更新 -> 创建和删除

  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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容

  • AngularJS是什么?AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是...
    200813阅读 1,596评论 0 3
  • 一、简介 1、 Vue.js 是什么 参考网址:https://cn.vuejs.org/v2/guide/ind...
    满天繁星_28c5阅读 472评论 0 1
  • 链接:https://cn.vuejs.org/v2/api/#Vue-filter API TPshop中国免费...
    zz云飞扬阅读 470评论 0 0
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,048评论 0 29
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139