看完这篇文章,不再害怕Vue3的源码(一)https://developer.aliyun.com/article/1426268
组件渲染
在 Vue 3 中,组件的渲染过程流程如下:
- 初始化渲染:当 Vue 3 创建一个组件实例时,Vue 3 会先对该组件进行初始化,并创建出其对应的 vnode。此时 Vue 3 将调用组件实例的 Setup 函数,来收集组件所需的响应式数据以及重新渲染组件所需的一些依赖关系。
- 模板编译:Vue 3 将会对组件的模板进行编译,将其转换为 VNode,并整理出 VNode 的父子关系。
- 虚拟 DOM 的生成:在此步骤中,Vue 3 将使用 VNode 即生成的虚拟 DOM 进行渲染,并将其插入到真实 DOM 中。
- 数据的更新:当组件内的数据发生变化时,Vue 3 将重复执行第二步至第四步的流程,生成新的 VNode,并将其与之前的 VNode 进行对比。
- DOM 的更新:在上一步中,当新旧虚拟节点进行比对后,Vue 3 将会根据比对结果执行相应的 DOM 操作
- 卸载流程:在组件卸载时,Vue 3 将从 DOM 中移除该组件,并进行一些相关的清理工作。
在 Vue 3 的渲染过程中,整个过程都是响应式的,也就是说,在响应式数据发生变化时,Vue 3 会自动重新渲染组件,这就是 Vue 3 状态响应式的核心机制。同时,Vue 3 的编译过程都是在运行时进行的,既可以使用 Vue 3 的模板语法,也可以直接使用 JavaScript 进行编写,使得 Vue 3 的渲染逻辑更加灵活。
VNode和diff算法
VNode 是 Vue 3 中的一个核心概念,它是一个关于视图层的抽象对象,包含了组件的层次结构和组件内部的状态信息。而 diff 算法是指在更新视图时,对比新旧 VNode 树,最小化地更改 DOM 树的算法,从而实现高效的视图更新。
在 Vue 3 中,当需要更新视图时,Vue 3 会通过对比新旧 VNode 树的差异,来确定需要哪些 DOM 操作,从而尽可能减少对 DOM 的操作,提高更新性能。
具体过程如下:
1. 对比 VNode:
Vue 3 会对新旧 VNode 树进行逐层对比,如果发现某个节点内容发生变化,或者子节点发生变化,就标记该节点需要更新。
2. 标记需要更新的节点:
对于标记为需要更新的节点,Vue 3 会将其放入队列中,标记为需要更新。
3. 执行更新:
Vue 3 会在下一次更新周期中,优先更新队列中的节点,从而完成对该节点的更新操作,这个过程中,Vue 3 使用了一些优化手段,以尽可能减少对 DOM 的操作,从而实现高效的视图更新。
需要注意的是,虽然 diff 算法可以有效地减少 DOM 的操作,但是在组件层级较深或者需要进行复杂计算时,仍然会影响页面性能,因此在开发时也需要谨慎优化。同时,对于静态节点尽量使用静态节点,可以通过开启 compileStatic 选项来把组件不需要改变的节点编译为静态节点,提升组件的性能。
异步组件和suspense
在 Vue 3 中,异步组件是指在组件渲染时,只有在需要时才会被加载的组件。
异步组件的加载可以通过 import
函数来实现懒加载,从而提高应用的性能。使用异步组件时,需要将其定义为一个返回 Promise
的函数,该函数执行后返回组件对象。
例如:
const AsyncComponent = () => import('./AsyncComponent.vue') • 1
另外,在 Vue 3 中,还引入了 Suspense
组件来解决异步组件加载时,产生的卡顿或是白屏问题。Suspense
组件可以在异步组件加载完成前,指定一个占位符组件,并在异步组件加载完成后,显示真正的组件。这样,可以在页面加载过程中避免出现卡顿或是白屏的现象,提高用户体验。
使用 Suspense 组件时,需要在模板中添加一个 组件,在其中包含一个或多个异步组件,并指定一个 fallback 组件作为占位符。示例如下:
<template> <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <div>Loading...</div> </template> </Suspense> </template>
需要注意的是,由于 Suspense 组件时 Vue 3 新引入的组件,因此在 Vue 2 中不支持。同时,使用异步组件和 Suspense 组件时也需要尽可能的避免触发过多的异步加载,避免影响页面性能。
V 编译器
模板解析和编译
在 Vue 3 中,模板解析和编译是 Vue 3 的核心功能之一,它们负责将模板转化为可执行的渲染函数,实现组件的渲染。
具体来说,模板解析和编译通过以下步骤来实现:
1. 解析模板:
Vue 3 会对模板进行解析,分析其中的 HTML 代码、指令以及用户自定义标签,并生成对应的 AST(抽象语法树)格式。
2. 生成渲染函数:
根据解析后的 AST,Vue 3 会对模板进行编译,生成可执行的渲染函数。在渲染函数中,Vue 3 会将模板中的数据绑定、计算属性和自定义指令等一系列特性编译为可执行的 JavaScript 代码,并通过生成闭包的方式实现了局部作用域,从而提高性能。
3. 生成 VNode:
在执行渲染函数时,Vue 3 会根据组件的状态信息,生成对应的 VNode(虚拟节点)树,这个 VNode 树中包含了组件的层次结构和组件内部数据以及指令等信息。
4. 插入到 DOM 树中:
最后,Vue 3 会将生成的 VNode 树和之前生成的旧的 VNode 树进行对比,并根据差异来决定 when 和 where 如何进行具体的 DOM 操作,最终将 VNode 树渲染到页面上。
需要注意的是,Vue 3 中的模板解析和编译都是在运行时进行的。这意味着 Vue 3 可以支持类似于 JSX 的模板语法,开发者可以将其视为模板那样编写,但实际上 Vue 3 会在运行时将其转化为可执行的 JavaScript 代码,从而实现高效的视图更新。同时,Vue 3 还引入了 compileStatic 选项,可以将组件的静态内容转换成静态节点,从而减少渲染时的重复计算和操作,从而提高组件的性能。
AST语法树
AST,全称是抽象语法树(Abstract Syntax Tree),是一种表示源代码抽象语法结构的树状结构。在 Vue 3 中,AST 被用于解析和编译模板,将模板转化为可执行的渲染函数。
在 Vue 3 中,模板首先会被解析成原始的 HTML 代码,包含了一些标签、属性和文本等内容。然后,Vue 3 会将这些原始的 HTML 代码,转换成 AST 树的形式,通过 AST 树可以表示出模板中各个元素的关系及作用,如父子关系,属性节点,文本节点等等。Vue 3 会在进行模板解析后,根据生成的 AST 树,利用编译器将其转换成可执行的 JavaScript 代码。这个过程中,Vue 3 会遍历 AST 树来寻找需要编译的部分,将其编译成原生 JavaScript 代码。
AST 有着广泛的应用,不仅仅是在 Vue 3 的模板解析和编译中使用。在前端开发中,很多工具和框架都会用到 AST 来实现相关功能,如 eslint、babel 等。AST 可以用于分析和优化代码的工具、代码的生成器和转换器等,可以大大提高代码的可维护性和开发效率。
需要注意的是,使用 AST 也需要注意它的性能,AST 节点过多或深度过大时,会导致解析速度变慢,影响应用性能。因此,开发者应该尽量避免出现过于复杂的 AST 树,从而提高应用的性能。
优化和静态提升
在 Vue 3 中,优化和静态提升是两种优化技术,用于提高组件的性能和减少重复渲染的次数。
1. 优化
优化是指在组件应用中,通过一些手段减少组件重新渲染的次数的技术。例如在使用 computed
属性的时候,只有当计算所依赖的数据发生变化时,才会进行重新计算和渲染视图,从而避免不必要的渲染。另外,Vue 3 还提供了 shouldUpdate 和 memo 方法,可以手动控制组件的重绘渲染,提高组件的性能。
2. 静态提升
静态提升是指在组件渲染时,把内部可能会执行多次的部分缓存起来,从而减少不必要的渲染次数。例如,在一个包含大量重复静态节点的组件中,可以使用 h() 函数把静态节点转换为 VNode 节点,从而避免不必要的重复渲染,提高组件的性能。
在 Vue 3 中,静态提升可以通过 compileStatic 选项来实现。compileStatic 选项可以将组件的静态内容转换成静态节点,并缓存起来,从而减少渲染时的重复计算和操作。开发者可以通过在模板中添加静态节点,使渲染时的计算量变小,从而提高应用的性能。
需要注意的是,在使用静态提升时,要避免将动态节点变为静态节点,否则会导致组件渲染不正常。同时,要尽可能地减少代码中的重复节点,从而提高静态提升的效果。
VI 其他重要概念和实现
插槽
在 Vue 3
中,插槽是一种将父组件中的内容传递到子组件中进行渲染的技术。
插槽功能可以让开发者在父组件中定义一些内容,然后将这些内容传递到子组件中进行渲染,从而实现更加灵活的组件设计。
在 Vue 3 中,可以使用 和
v-slot
指令来定义和使用插槽。
以下是使用插槽的基本步骤:
1. 在父组件中,使用 标签定义插槽。可以通过
name
属性来给插槽命名。
<template> <div> <slot name="header"></slot> <slot></slot> <slot name="footer"></slot> </div> </template>
2. 在子组件中,使用 v-slot
指令来引用父组件中定义的插槽。可以使用 v-slot 的简写语法 # ,并通过 slot 属性来引用对应的插槽。
<template> <div> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template>
<template> <BaseLayout> <template #header> <h1>Welcome</h1> </template> <template> <p>Hello, Vue 3!</p> </template> <template #footer> <p>Have a nice day!</p> </template> </BaseLayout> </template>
需要注意的是,在 Vue 3
中,通过 v-slot
可以使用插槽的具名和默认语法,而在 Vue 2 中仅支持具名插槽。
Vue 3
的插槽功能可以大大提高组件的灵活性,使得父组件可以更加自由地向子组件中传递数据和内容。
看完这篇文章,不再害怕Vue3的源码(三)https://developer.aliyun.com/article/1426271