Vue3 新特性
Fragment、Teleport、Suspense 及编译优化
问题
Vue3 引入了哪些新特性?包括 Fragment、Teleport、Suspense、Tree-shaking 优化、PatchFlag、HoistStatic。
解答
Fragment
Vue3 组件可以有多个根节点,不再需要单一根元素包裹。
<!-- Vue2 必须有单一根节点 -->
<template>
<div>
<header>头部</header>
<main>内容</main>
</div>
</template>
<!-- Vue3 支持多根节点 -->
<template>
<header>头部</header>
<main>内容</main>
<footer>底部</footer>
</template>
Teleport
将组件内容渲染到 DOM 中的任意位置,常用于模态框、通知等场景。
<template>
<button @click="showModal = true">打开弹窗</button>
<!-- 将内容传送到 body 下 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>这是一个模态框</p>
<button @click="showModal = false">关闭</button>
</div>
</Teleport>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
</style>
Suspense
处理异步组件的加载状态,提供 loading 和 fallback 机制。
<template>
<Suspense>
<!-- 异步组件 -->
<template #default>
<AsyncComponent />
</template>
<!-- 加载中显示的内容 -->
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
// 异步组件
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
)
</script>
<!-- AsyncComponent.vue -->
<template>
<div>{{ data }}</div>
</template>
<script setup>
// 组件内使用 async setup
const response = await fetch('/api/data')
const data = await response.json()
</script>
Tree-shaking 优化
Vue3 采用模块化设计,未使用的 API 会被打包工具移除。
// Vue2 - 全量引入
import Vue from 'vue'
Vue.nextTick(() => {})
// Vue3 - 按需引入,未使用的不会打包
import { ref, computed, watch, nextTick } from 'vue'
// 只用了 ref,其他 API 会被 tree-shake 掉
const count = ref(0)
PatchFlag(静态标记)
编译时标记动态内容,运行时只对比动态部分,跳过静态内容。
<template>
<div>
<p>静态文本</p>
<p>{{ message }}</p>
<p :class="className">动态 class</p>
</div>
</template>
编译后的渲染函数:
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
// 静态节点,无 patchFlag
_createElementVNode("p", null, "静态文本"),
// patchFlag = 1,表示动态文本
_createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */),
// patchFlag = 2,表示动态 class
_createElementVNode("p", {
class: _normalizeClass(_ctx.className)
}, "动态 class", 2 /* CLASS */)
]))
}
常见 PatchFlag 值:
export const enum PatchFlags {
TEXT = 1, // 动态文本
CLASS = 2, // 动态 class
STYLE = 4, // 动态 style
PROPS = 8, // 动态属性(非 class/style)
FULL_PROPS = 16, // 有动态 key 的属性
HYDRATE_EVENTS = 32,
STABLE_FRAGMENT = 64,
KEYED_FRAGMENT = 128,
UNKEYED_FRAGMENT = 256,
NEED_PATCH = 512,
DYNAMIC_SLOTS = 1024,
HOISTED = -1, // 静态提升
BAIL = -2 // 退出优化模式
}
HoistStatic(静态提升)
将静态节点提升到渲染函数外部,避免重复创建。
<template>
<div>
<p>静态内容1</p>
<p>静态内容2</p>
<p>{{ dynamic }}</p>
</div>
</template>
编译后:
// 静态节点被提升到模块顶层,只创建一次
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "静态内容1", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("p", null, "静态内容2", -1 /* HOISTED */)
export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1, // 复用静态节点
_hoisted_2, // 复用静态节点
_createElementVNode("p", null, _toDisplayString(_ctx.dynamic), 1 /* TEXT */)
]))
}
编译优化对比
// Vue2 diff - 全量对比
// 每次更新都要遍历整个 vnode 树
// Vue3 diff - 靶向更新
// 1. 静态节点提升,不参与 diff
// 2. PatchFlag 标记动态内容类型
// 3. 只对比带有 patchFlag 的节点
// 4. 根据 patchFlag 值决定对比哪些属性
关键点
- Fragment:支持多根节点,减少无意义的 DOM 层级
- Teleport:将内容渲染到指定 DOM 位置,解决 CSS 层级问题
- Suspense:统一处理异步组件加载状态
- Tree-shaking:模块化设计,按需引入,减小打包体积
- PatchFlag:编译时标记动态内容类型,运行时精准更新
- HoistStatic:静态节点提升到渲染函数外,避免重复创建
目录