31-mini-vue 更新element的children

更新element的children

  1. 实现的功能:
    1. 我们更新为 Text,原来的节点是Array
    2. 我们更新为 Text,原来的节点是Text
    3. 我们更新为 Array, 原来的节点是Text
    4. 我们更新为 Array, 原来的节点是Array
  2. 初始化的文件,我们在 this 上挂载一个变量布尔值,后续通过控制台动态修改该变量的值,来实现更新功能
    1. index.html
    2. main.js
    3. App.js
js 复制代码
     // App.js
     import {
         h,
         ref
     } from "../../lib/guide-mini-vue.esm.js";
     import {
         ArrayToText
     } from "./ArrayToText.js";
     export const App = {
         name: "App",
         render() {
             return h("div", {},
                 [
                     h('p', {}, '主页'),
                     h(ArrayToText)
                 ]
             )
         },
         setup() {
             return {}
         },
     }
js 复制代码
     // ArrayToText.js
     import {
         h,
         ref
     } from '../../lib/guide-mini-vue.esm.js'

     const nextChildren = "newChildren"
     const prevChildren = [h('div', {}, "A"), h('div', {}, "B")]

     export const ArrayToText = {
         name: "ArrayToText",
         setup() {
             const isChange = ref(false); // 挂载布尔值
             window.isChange = isChange;
             return {
                 isChange,
             }
         },
         render() {
             const self = this;
             return self.isChange === true // 使用布尔值来进行条件判断,方便控制台控制,进行更新为 text
                 ?
                 h("div", {}, nextChildren) : h("div", {}, prevChildren);
         }
     }
js 复制代码
    // TextToArray.js

    import {
        h,
        ref
    } from '../../lib/guide-mini-vue.esm.js'

    const prevChildren = "oldChildren"
    const nextChildren = [h('div', {}, "A"), h('div', {}, "B")]

    export const TextToArray = {
        name: "ArrayToText",
        setup() {
            const isChange = ref(false);
            window.isChange = isChange;
            return {
                isChange,
            }
        },
        render() {
            const self = this;
            return self.isChange === true ?
                h("div", {}, nextChildren) : h("div", {}, prevChildren);
        }
    }
js 复制代码
      import {
          h,
          ref
      } from '../../lib/guide-mini-vue.esm.js'

      const nextChildren = "newChildren"
      const prevChildren = "oldChildren"

      export const TextToText = {
          name: "TextToText",
          setup() {
              const isChange = ref(false);
              window.isChange = isChange;
              return {
                  isChange,
              }
          },
          render() {
              const self = this;
              return self.isChange === true ?
                  h("div", {}, nextChildren) : h("div", {}, prevChildren);
          }
      }
  1. 实现更新 我们更新为 Text,原来的节点是Array
js 复制代码
      // renderer.ts
      // 处理元素节点:如果 n1 不存在则说明是初次挂载(mount),否则为更新(patch)
      // parentComponent 用于在挂载子节点时传递上下文(例如组件实例)
      function processElement(n1, n2, container, parentComponent) {
          if (!n1) {
              // 这里进行初次挂载,将 n2 渲染为真实 DOM 并插入 container
              mountElement(n2, container, parentComponent)
          } else {
              // 已存在则执行补丁流程,将 n1(旧 vnode)更新为 n2(新 vnode)
              patchElement(n1, n2, container)
          }
      }

      function patchElement(n1, n2, container) {

          // 提取旧 props 和 新 props,保证为对象(避免 undefined)
          let oldProps = n1.props || EMPTY_OBJECT
          let newProps = n2.props || EMPTY_OBJECT

          // 复用旧 vnode 的真实元素 el 给新 vnode,后续直接在 el 上进行更新
          let el = n2.el = n1.el
          // 先更新 children 再更新 props(顺序可以影响 DOM 操作和性能)
          patchChildren(n1, n2, el) // ✅    
          // 最后对 props 进行差异更新
          patchProps(el, oldProps, newProps)
      }

      function patchChildren(n1, n2, container) { // ✅  
          // 处理 children 的更新:根据 shapeFlag 判断之前/之后 children 的类型(文本或数组)
          const {
              shapeFlag: prevShapeFlag,
          } = n1
          const {
              shapeFlag: nextShapeFlag,
              children: c2
          } = n2

          // 情况:旧的是数组,新的是文本(Array -> Text)
          if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
              if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
                  // 卸载旧的数组子节点(移除对应 DOM)
                  unmountChildren(n1.children)
                  // 将容器设置为新的文本内容
                  hostSetElementText(container, c2)
              }
          }
      }

      function unmountChildren(children) { // ✅  
          // 卸载一组子节点:遍历 children,调用宿主平台的删除方法移除对应的真实元素
          children.forEach(child => {
              const el = child.el
              hostPatchRemove(el) // ✅  
          })
      }
  1. 我们更新为 Text,原来的节点是Text,
js 复制代码
  // renderer.ts
  // 情况:新的是文本(Text)
  function patchChildren(n1, n2, container) {
      const {
          shapeFlag: prevShapeFlag,
          children: c1
      } = n1
      const {
          shapeFlag: nextShapeFlag,
          children: c2
      } = n2

      if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
          if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
              // 如果旧的是数组,需要先卸载数组子节点再设置文本
              unmountChildren(n1.children)
              hostSetElementText(container, c2)
          } else { // ✅  旧的也是文本的情况
              // 只有在新旧文本内容不一致时,才替换元素文本,避免不必要的 DOM 操作
              if (c1 !== c2) {
                  hostSetElementText(container, c2)
              }
          }
      }
  }
  // 上面代码的优化版本:将重复逻辑拆成更通用的步骤,先处理数组->其他情况的卸载,再统一处理文本替换
  function patchChildren(n1, n2, container, parentComponent) {
      const {
          shapeFlag: prevShapeFlag,
          children: c1
      } = n1
      const {
          shapeFlag: nextShapeFlag,
          children: c2
      } = n2

      if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
          // 如果之前是数组,则先卸载旧数组子节点
          if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
              unmountChildren(n1.children)
          }
          // 只有新旧文本不同,才进行文本替换,减少 DOM 操作
          if (c1 !== c2) {
              hostSetElementText(container, c2)
          }
      }
  }
  1. 我们更新为 Array, 原来的节点是Text
js 复制代码
  // renderer.ts
  function patchChildren(n1, n2, container, parentComponent) {
      const {
          shapeFlag: prevShapeFlag,
          children: c1
      } = n1
      const {
          shapeFlag: nextShapeFlag,
          children: c2
      } = n2

      if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
          // 新的是文本的情况(Text)
          if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
              // 旧的是数组 -> 卸载旧子节点
              unmountChildren(n1.children)
          }
          if (c1 !== c2) {
              // 新旧文本内容不同时替换文本
              hostSetElementText(container, c2)
          }
      } else {
          // ✅ 新的是数组类型(Array)
          if (prevShapeFlag & shapeFlags.TEXT_CHILDREN) {
              // 旧的是文本 -> 先清空容器的文本,再挂载新的数组子节点
              hostSetElementText(container, "")
              mountChildren(c2, container, parentComponent)
          } else {
              // 旧的也是数组:这里应当进行更复杂的数组  diff 和更新逻辑(后续实现)
          }
      }
  }
  1. 我们更新为 Array, 原来的节点是Array,对比比较复杂,使用到了 diff 算法,下一节更新
相关推荐
2501_920931704 小时前
React Native鸿蒙跨平台采用ScrollView的horizontal属性实现横向滚动实现特色游戏轮播和分类导航
javascript·react native·react.js·游戏·ecmascript·harmonyos
0思必得06 小时前
[Web自动化] Selenium处理动态网页
前端·爬虫·python·selenium·自动化
东东5166 小时前
智能社区管理系统的设计与实现ssm+vue
前端·javascript·vue.js·毕业设计·毕设
catino6 小时前
图片、文件的预览
前端·javascript
2501_920931708 小时前
React Native鸿蒙跨平台实现推箱子游戏,完成玩家移动与箱子推动,当所有箱子都被推到目标位置时,玩家获胜
javascript·react native·react.js·游戏·ecmascript·harmonyos
layman05288 小时前
webpack5 css-loader:从基础到原理
前端·css·webpack
半桔8 小时前
【前端小站】CSS 样式美学:从基础语法到界面精筑的实战宝典
前端·css·html
AI老李8 小时前
PostCSS完全指南:功能/配置/插件/SourceMap/AST/插件开发/自定义语法
前端·javascript·postcss
_OP_CHEN8 小时前
【前端开发之CSS】(一)初识 CSS:网页化妆术的终极指南,新手也能轻松拿捏页面美化!
前端·css·html·网页开发·样式表·界面美化
啊哈一半醒8 小时前
CSS 主流布局
前端·css·css布局·标准流 浮动 定位·flex grid 响应式布局