Vue.js技术内幕 3-8 组件

组件

  • 组件,是对DOM结构的一种抽象。一个大型应用可以通过独立且可复用的多个组件实现。
  • 当数据更新后,组件可以自动重新渲染,用户只需要关心数据逻辑的处理,无须关心底层DOM操作。

1. 组件的渲染

  • vnode,用来描述DOM的JS对象,可以描述元素节点、组件节点、纯文本节点、注释节点等。
  • 组件vnode,对抽象事物的描述,最终会渲染为组件内部定义的HTML标签。
  • 组件渲染本质,将各种类型的vnode渲染成真实的DOM。
  • 组件渲染流程:创建vnode => 渲染vnode => 生成DOM
  1. 创建vnode

    • 创建元素vnode:createElementVNode
    • 创建组件vnode:createVNode
  2. 组件的挂载

    • 普通元素的挂载 mountElement
      • 创建DOM元素节点
      • 处理children
      • 处理props
      • 挂载DOM到container
    • 组件挂载函数 mountComponent
      • 创建组件实例,通过对象的方式创建当前渲染的组件实例
      • 设置组件实例,组件上下文,props、slots等其他实例属性等初始化处理
      • 设置并运行带副作用的渲染函数
        • 渲染组件生成subTree:renderComponentRoot
        • 把subTree挂载到container:patch,根据vnode挂载DOM,根据新vnode更新DOM
    • 组件的嵌套挂载 mountComponent
      • 递归调用mountComponent

2. 组件的更新

  1. 更新组件

    • 更新组件vnode节点
    • 渲染新的子树vnode
    • 根据新旧子树vnode执行patch逻辑
  2. Diff策略:

    • 同层比较,深度优先。
    • 不同节点:销毁旧节点,创建新节点。
    • 相同节点:根据不同的vnode类型执行不同的处理逻辑,如普通元素类型和组件类型的处理过程。调用 patchVnode。
      • 组件的更新updateComponent

        • (1)检查是否需要更新:Vue 会检查组件的 props 是否发生变化,或者组件自身状态变化触发了重新渲染。
        • (2)更新组件属性:如果需要更新,会更新组件实例的 props 等属性。
        • (3)强制重新渲染:最终会通过之前 setupRenderEffect 创建的 effect 来调度组件的重新渲染。重新渲染同样会生成新的 subTree,然后递归调用 patch 对比新旧subTree。
      • 元素的更新

        • 更新props:patchProps函数会更新DOM节点的c1ass、style、event以及其他的一些DOM属性。

        • 更新子节点:子节点类型有:纯文本、vnode数组、空,新旧子节点对比就有3*3=9种情况

          • (1)旧子节点是纯文本
            1. 新子节点也是纯文本,直接文本替换
            2. 新子节点为空,删除旧子节点
            3. 新子节点为数组,先把旧子节点的文本清空,再在旧子节点的父容器下添加多个新子节点
          • (2)旧子节点是空
            1. 新子节点也是纯文本,直接在旧子节点的父容器下添加新文本节点即可
            2. 新子节点为空,什么都不做
            3. 新子节点为数组,直接在旧子节点的父容器下添加多个新子节点
          • (3)旧子节点是vnode数组的情况
            1. 新子节点也是纯文本,先删除旧子节点,再在旧子节点的父容器下添加新的文本节点
            2. 新子节点为空,删除旧子节点
            3. 新子节点为数组,利用核心diff算法处理新旧子节点
        • 核心diff算法:快速diff + 最长递归子序列

          1. 新头 VS 旧头,从头部开始,依次对比新节点和旧节点,如果相同,则执行path更新节点,如果不同或者头部索引大于旧节点尾部索引或者新节点尾部索引,则同步过程结束
          2. 新尾 VS 旧尾,从尾部开始,依次对比新节点和旧节点,如果相同,则执行patch更新节点,如果不同或者头部索引大于旧节点尾部索引或者新节点尾部索引,则同步过程结束
          3. 头头比较、尾尾比较,之后,只有三种情况要处理:
            • (1)新子节点有剩余,要添加新节点
            • (2)旧子节点有剩余,要删除多余节点
            • (3)未知子序列,定义source数组,长度为新的一组子节点在经过头头、尾尾比较之后剩余未处理的元素个数,存储新子序列节点在旧子序列节点中的位置索引,用于确定最长递增子序列。不属于最长递增子序列中的元素进行移动。
              • 最长递增子序列的求解算法:
                • 遍历数组,依次求解在长度为i时的最长递增子序列,当i元素大于i-1元素时,添加i元素并更新最长子序列,否则继续往前查找,直到找到一个比i小的元素,然后将其插在该元素后面并更新对应的最长子序列。目的:让递增序列的差尽可能小,从而获得更长的递增子序列。

3. 组件的实例

  • 组件实例,用于维护组件的上下文数据,如props、data、vnode、render函数、生命周期钩子函数等。
  • 设置组件实例,setupComponent,初始化props、初始化插槽、设置有状态的组件实例。

4. 组件的props

  • props配置标准化:字符串数组或对象形式灵活定义props,对灵活输入做标准化处理。
  • props值的初始化:设置props的值,验证props是否合法,把props变成响应式的,以及将其添加到实例上。当组件传入的props数据发生变化时,会触发子组件的重新渲染。
  • 设置props的值:遍历props数据,获取每个key对应的值并赋值给props或者attrs,以及对需要转换的props求值。
  • 验证props是否合法:遍历标准化后的props配置对象,获取每一个配置opt,然后执行validateProp进行验证。
  • props的更新:触发子组件重新渲染,组件的重新渲染会触发patch过程。然后遍历子节点,递归patch。

5. 组件的生命周期

  • Vue 组件的生命周期是一个组件从创建、挂载到页面、更新、再到卸载销毁的整个过程。生命周期钩子函数是这个过程的关键时间点,Vue 会在这些时间点调用我们注册的函数。即在特定的时机执行特定的逻辑。
  1. 组件初始化阶段 (setup / beforeCreate / created)

    • setup():
      • Composition API 的入口,几乎所有其他的钩子都在这里或通过这里注册。
      • 定义响应式数据 (ref, reactive)、定义方法、注册生命周期钩子 (onMounted, onUpdated 等)、定义计算属性 (computed) 和 侦听器 (watch)、return 所有需要暴露给模板的内容。
      • 在 setup 中,它只是“注册”,执行是在未来的挂载阶段。
  2. 组件挂载阶段 (onBeforeMount / onMounted)

    • onBeforeMount:在组件被挂载到 DOM之前执行。此时,模板已编译,但还未生成真实 DOM。

    • onMounted:组件挂载之后执行。组件及其子组件都已被挂载到真实的 DOM 中。

    • 常见用途:

      • 操作 DOM:此时能获取到 DOM 元素。
      • 发送 Ajax 请求:从服务器获取数据,然后填充到组件中。
      • 初始化第三方库:例如初始化图表库 (ECharts)、地图、轮播图等需要操作 DOM 的库。
      • 设置事件监听器(但通常更推荐用 v-on 或 watch)。
  3. 组件更新阶段 (onBeforeUpdate / onUpdated)

    • onBeforeUpdate:组件更新之前执行。此时 DOM 还未更新。用途:用于获取更新前的 DOM 状态(如滚动位置)。
    • onUpdated:组件更新之后执行。此时 DOM 已更新。用途:在数据变化后操作更新后的 DOM。
    • 注意:不要在这个钩子里更改状态,这可能会导致无限的更新循环!
  4. 组件卸载阶段 (onBeforeUnmount / onUnmounted)

    • onBeforeUnmount:组件销毁之前执行。
    • onUnmounted:组件销毁之后执行。
    • 常见用途:
      • 执行清理操作,防止内存泄漏。
      • 清除定时器 (clearInterval, clearTimeout)。
      • 取消事件监听器 (removeEventListener)。
      • 取消未完成的网络请求(例如 Axios 的 CancelToken)。
      • 清理第三方库的实例(如销毁 ECharts 实例、地图实例)。
  5. 其他实用钩子

    • onErrorCaptured:捕获来自后代组件的错误,可根据错误类型和信息统计并上报。用于编写组件错误边界。
    • onActivated / onDeactivated:用于缓存组件状态。

6. 异步组件

  • defineAsyncComponent主要做了三件事情:渲染占位节点,加载异步JavaScript模块以获取组件对象,以及重新渲染组件。const AsyncComp = defineAsyncComponent(() => { import('./components/A.vue')})
  • 异步组件高级用法:defineAsyncComponent可以接收一个对象,允许配置加载中的组件loadingComponent、加载失败的组件errorComponent、加载失败后的错误处理函数,以及delay延时渲染loadingComponent和timeout超时渲染errorComponent等属性。
  • 即使多个异步组件同步加载,也只会发送—个异步请求;如果已经被加载的异步组件被再次初始化,则直接获取对应的组件对象并渲染。



参考&感谢各路大神

  • [Vue.js技术内幕-黄轶]
posted @ 2024-09-20 10:58  安静的嘶吼  阅读(3)  评论(0)    收藏  举报