Vue.js技术内幕 3-8 组件
组件
- 组件,是对DOM结构的一种抽象。一个大型应用可以通过独立且可复用的多个组件实现。
- 当数据更新后,组件可以自动重新渲染,用户只需要关心数据逻辑的处理,无须关心底层DOM操作。
1. 组件的渲染
- vnode,用来描述DOM的JS对象,可以描述元素节点、组件节点、纯文本节点、注释节点等。
- 组件vnode,对抽象事物的描述,最终会渲染为组件内部定义的HTML标签。
- 组件渲染本质,将各种类型的vnode渲染成真实的DOM。
- 组件渲染流程:创建vnode => 渲染vnode => 生成DOM
-
创建vnode
- 创建元素vnode:createElementVNode
- 创建组件vnode:createVNode
-
组件的挂载
- 普通元素的挂载 mountElement
- 创建DOM元素节点
- 处理children
- 处理props
- 挂载DOM到container
- 组件挂载函数 mountComponent
- 创建组件实例,通过对象的方式创建当前渲染的组件实例
- 设置组件实例,组件上下文,props、slots等其他实例属性等初始化处理
- 设置并运行带副作用的渲染函数
- 渲染组件生成subTree:renderComponentRoot
- 把subTree挂载到container:patch,根据vnode挂载DOM,根据新vnode更新DOM
- 组件的嵌套挂载 mountComponent
- 递归调用mountComponent
- 普通元素的挂载 mountElement
2. 组件的更新
-
更新组件
- 更新组件vnode节点
- 渲染新的子树vnode
- 根据新旧子树vnode执行patch逻辑
-
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)旧子节点是纯文本
- 新子节点也是纯文本,直接文本替换
- 新子节点为空,删除旧子节点
- 新子节点为数组,先把旧子节点的文本清空,再在旧子节点的父容器下添加多个新子节点
- (2)旧子节点是空
- 新子节点也是纯文本,直接在旧子节点的父容器下添加新文本节点即可
- 新子节点为空,什么都不做
- 新子节点为数组,直接在旧子节点的父容器下添加多个新子节点
- (3)旧子节点是vnode数组的情况
- 新子节点也是纯文本,先删除旧子节点,再在旧子节点的父容器下添加新的文本节点
- 新子节点为空,删除旧子节点
- 新子节点为数组,利用核心diff算法处理新旧子节点
- (1)旧子节点是纯文本
-
核心diff算法:快速diff + 最长递归子序列
- 新头 VS 旧头,从头部开始,依次对比新节点和旧节点,如果相同,则执行path更新节点,如果不同或者头部索引大于旧节点尾部索引或者新节点尾部索引,则同步过程结束
- 新尾 VS 旧尾,从尾部开始,依次对比新节点和旧节点,如果相同,则执行patch更新节点,如果不同或者头部索引大于旧节点尾部索引或者新节点尾部索引,则同步过程结束
- 头头比较、尾尾比较,之后,只有三种情况要处理:
- (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 会在这些时间点调用我们注册的函数。即在特定的时机执行特定的逻辑。
-
组件初始化阶段 (setup / beforeCreate / created)
- setup():
- Composition API 的入口,几乎所有其他的钩子都在这里或通过这里注册。
- 定义响应式数据 (ref, reactive)、定义方法、注册生命周期钩子 (onMounted, onUpdated 等)、定义计算属性 (computed) 和 侦听器 (watch)、return 所有需要暴露给模板的内容。
- 在 setup 中,它只是“注册”,执行是在未来的挂载阶段。
- setup():
-
组件挂载阶段 (onBeforeMount / onMounted)
-
onBeforeMount:在组件被挂载到 DOM之前执行。此时,模板已编译,但还未生成真实 DOM。
-
onMounted:组件挂载之后执行。组件及其子组件都已被挂载到真实的 DOM 中。
-
常见用途:
- 操作 DOM:此时能获取到 DOM 元素。
- 发送 Ajax 请求:从服务器获取数据,然后填充到组件中。
- 初始化第三方库:例如初始化图表库 (ECharts)、地图、轮播图等需要操作 DOM 的库。
- 设置事件监听器(但通常更推荐用 v-on 或 watch)。
-
-
组件更新阶段 (onBeforeUpdate / onUpdated)
- onBeforeUpdate:组件更新之前执行。此时 DOM 还未更新。用途:用于获取更新前的 DOM 状态(如滚动位置)。
- onUpdated:组件更新之后执行。此时 DOM 已更新。用途:在数据变化后操作更新后的 DOM。
- 注意:不要在这个钩子里更改状态,这可能会导致无限的更新循环!
-
组件卸载阶段 (onBeforeUnmount / onUnmounted)
- onBeforeUnmount:组件销毁之前执行。
- onUnmounted:组件销毁之后执行。
- 常见用途:
- 执行清理操作,防止内存泄漏。
- 清除定时器 (clearInterval, clearTimeout)。
- 取消事件监听器 (removeEventListener)。
- 取消未完成的网络请求(例如 Axios 的 CancelToken)。
- 清理第三方库的实例(如销毁 ECharts 实例、地图实例)。
-
其他实用钩子
- onErrorCaptured:捕获来自后代组件的错误,可根据错误类型和信息统计并上报。用于编写组件错误边界。
- onActivated / onDeactivated:用于缓存组件状态。
6. 异步组件
- defineAsyncComponent主要做了三件事情:渲染占位节点,加载异步JavaScript模块以获取组件对象,以及重新渲染组件。
const AsyncComp = defineAsyncComponent(() => { import('./components/A.vue')})
- 异步组件高级用法:defineAsyncComponent可以接收一个对象,允许配置加载中的组件loadingComponent、加载失败的组件errorComponent、加载失败后的错误处理函数,以及delay延时渲染loadingComponent和timeout超时渲染errorComponent等属性。
- 即使多个异步组件同步加载,也只会发送—个异步请求;如果已经被加载的异步组件被再次初始化,则直接获取对应的组件对象并渲染。
参考&感谢各路大神
- [Vue.js技术内幕-黄轶]
宝剑锋从磨砺出,梅花香自苦寒来。