Vue3
Composition API
Vue3 新增了 Composition API,它是一组基于函数的 API,可以更灵活地组织和复用组件逻辑。解决了 Vue2 中选项式 API 带来的逻辑碎片化问题,尤其适合大型复杂项目。
在 Vue2 中,逻辑复用主要依赖 mixins、高阶组件和自定义指令,但这些方式存在命名冲突、数据来源不清晰等问题。而 Composition API 允许将组件逻辑提取为独立的函数(通常称为 "组合函数"),这些函数可以在多个组件间共享:
下面是一个简单的示例:
// useCounter.js - 一个可复用的计数器逻辑
import { ref, computed, onMounted } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const doubleCount = computed(() => count.value * 2)
// 生命周期钩子也可以包含在复用逻辑中
onMounted(() => {
console.log('Counter initialized')
})
return {
count,
increment,
decrement,
doubleCount
}
}
在组件中使用这个组合函数:
import { useCounter } from './useCounter'
export default {
setup() {
const { count, increment, doubleCount } = useCounter(10)
return { count, increment, doubleCount }
}
}
非常类似于React Hook,但是Vue3 的 Composition API 允许在组件的 setup
函数中使用这些组合函数,能在条件或循环语句中调用,且可以更好地控制响应式数据的生命周期。
为什么Composition API可以在循环或条件语句中使用,而react hook不行?
1. React Hook 的调用规章限制
- React Hook 的核心机制是通过调用顺序来识别和关联状态:
- 每次组件渲染时,Hook 的调用顺序必须保持一致。
- React 内部维护一个链表,按顺序存储每个 Hook 的状态。
- 如果在循环或条件中调用 Hook,会破坏调用顺序的一致性:
- 例如,条件不满足时跳过某个 Hook,会导致后续 Hook 的索引错位。
- 这会使 React 错误地将状态与 Hook 关联,引发难以调试的 Bug(如状态值错乱
2. Vue Composition API 的灵活性
- (1)基于显式的 setup() 初始化,Vue3 的组件逻辑通过 setup() 函数一次性初始化:
- 组合函数(如 useFetch)在 setup() 执行时被调用,返回的响应式状态会被缓存。
- 后续渲染时直接使用已创建的状态,无需重新调用组合函数。
- (2)响应式系统自动追踪依赖,Vue 的响应式系统主动追踪依赖:
- 当状态变化时,Vue 会自动找到使用该状态的 DOM 节点并更新。
- 组合函数的调用位置不影响依赖关系,因为依赖是在运行时通过 getter/setter 追踪的。
虚拟 DOM 重写
Vue3 对虚拟 DOM 算法进行了彻底重写,主要优化点包括:
- Patch Flag(编译时标记)
- 原理:在编译阶段分析模板,为动态节点添加标记(如 TEXT、CLASS、STYLE 等),只更新需要变化的部分。
- 效果:减少了运行时的 Diff 计算量,提升渲染速度
<!-- 编译后会标记动态文本节点 -->
<div>
<span>{{ message }}</span> <!-- 标记为 TEXT -->
<span>静态文本</span> <!-- 完全跳过 Diff -->
</div>
- 静态提升
- 原理:将模板中不变的静态节点提升到渲染函数外部,避免每次渲染都重新创建。
- 效果:减少了内存分配和垃圾回收,提高渲染性能。
// 编译前
const render = () => <div>静态内容</div>;
// 编译后(静态内容被提升)
const staticNode = <div>静态内容</div>;
const render = () => staticNode;
Proxy 响应式
Vue3 使用 ES6 Proxy 替代 Vue2 的 Object.defineProperty,带来以下优势:
- 更高效的响应式追踪
- 深度监听:Proxy 可以直接监听对象 / 数组的变化,无需像 Vue2 那样递归遍历所有属性。
- 新增 / 删除属性支持:Vue2 无法检测对象属性的新增或删除,而 Proxy 可以。
const obj = reactive({});
obj.newProp = 123; // Vue3 可直接追踪新增属性
- 更少的内存开销
- Proxy 是原生对象,相比 Vue2 的响应式包装对象,内存占用更小。
编译时优化
Vue3 的编译器(@vue/compiler-dom)引入了多项编译时优化:
- 区块(Block)系统
- 原理:将模板划分为多个区块,每个区块内的动态节点形成一棵稳定的树结构,减少 Diff 范围。
- 效果:提升复杂嵌套结构的更新效率。
<!-- 编译后会将 v-if/v-for 等指令转换为区块 -->
<template>
<div>
<div v-if="condition">动态内容</div> <!-- 独立区块 -->
<ul>
<li v-for="item in list">{{ item }}</li> <!-- 列表区块 -->
</ul>
</div>
</template>
- 事件缓存
- 原理:对静态事件处理函数(如 @click="handleClick")进行缓存,避免重复创建。
- 效果:减少内存占用和渲染开销。
组件初始化优化
Vue3 重写了组件初始化流程,提升组件创建速度:
- 更轻量的组件实例 :Vue3 的组件实例体积比 Vue2 减少约 40%,内存占用更低。
- 更快的创建速度 :通过优化组件选项合并和初始化逻辑,组件创建速度提升约 1.3~2 倍。
Tree-shaking 支持
- Vue3 重构为基于 ES 模块的设计,支持 Tree-shaking(按需打包):
- Vue3 基础运行时体积约 11KB(gzipped),相比 Vue2 减少约 41%。
Typescript支持
Vue3 从底层架构开始就采用 TypeScript 重写,而 Vue2 是用 JavaScript 编写的,后期通过类型声明文件(.d.ts)提供有限支持。
自定义渲染器
Vue3 提供了 @vue/runtime-core,允许创建自定义渲染器(如渲染到 WebGL、原生应用等):
- 更灵活的渲染目标: 可针对特定场景(如游戏、桌面应用)优化渲染性能。
- 更低的渲染层抽象成本: 相比 Vue2,减少了中间层的性能损耗。
新的内置组件
1. Teleport 组件
允许将组件内容渲染到 DOM 中的其他位置,而不必受限于组件的嵌套层级。常用于创建模态框、弹窗等需要脱离父容器布局的场景。
应用场景 :模态框 / 对话框 , 悬浮提示(Tooltip) , 下拉菜单
2. Suspense 组件
用于处理异步组件的加载状态,提供优雅的加载中(fallback)体验。可与异步组件(defineAsyncComponent)和组合式 API 中的 async setup() 配合使用。
<template>
<Suspense>
<!-- 异步组件 -->
<template #default>
<AsyncComponent />
</template>
<!-- 加载中状态 -->
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
export default {
components: {
AsyncComponent
}
};
</script>
多根结点 Fragment
Vue3 支持组件返回多个根节点(Fragment),不再需要包裹在单一根节点内。这使得组件结构更灵活,减少了不必要的 DOM 层级。
<!-- Vue3 中合法 -->
<template>
<header>页面头部</header>
<main>主要内容</main>
<footer>页面底部</footer>
</template>