Appearance
Vue3 新特性
Composition API (组合式)
Composition API 是 Vue3 引入的组件组织方式,重点在于逻辑复用和按关注点拆分代码。相较于 Vue2 的 Options API,它更适合处理大型组件中的跨区域逻辑聚合。
简单计数器在 Vue2 中的写法:
js
export default {
data() {
return {
count: 0,
}
},
methods: {
increment() {
this.count++
},
},
watch: {
count(newVal) {
console.log('count changed:', newVal)
},
},
}对应的 Vue3 写法:
js
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
watch(count, (newVal) => {
console.log('count changed:', newVal)
})
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>计数器逻辑还可以继续抽成 composable:
js
function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// 在组件中使用
const { count, increment } = useCounter()相对于 Options API,Composition API 的主要特点:
- 集中组织逻辑
- 可复用性高
- 对 TypeScript 的类型推断支持度更高
- 组件的维护更方便
setup
setup() 是组合式 API 的入口函数,主要用于在组件初始化阶段声明响应式状态、组合逻辑以及对外暴露模板可见的绑定。
基本使用:
js
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
count
}
},
mounted() {
console.log(this.count) // 0
}
}
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>setup() 有两个参数:
- props:和标准的组件一致,一个 setup 函数的 props 是响应式的,并且会在传入新的 props 时同步更新。
- context:第二个参数是 Setup 上下文对象,会暴露在 setup 中常用的一些能力:
js
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
},
}setup() 的几个边界:
- setup() 不包含对组件实例的访问权,在 setup() 顶层访问 this 会是 undefined
setup()应返回同步对象。只有组件作为Suspense后裔时,才允许使用async setup()。
<script setup> 是 setup() 的语法糖,用于在单文件组件中直接声明组合式逻辑。
js
<script setup>
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>等价于
js
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
// 变量
const msg = 'Hello!'
// 函数
function log() {
console.log(msg)
}
// 暴露给模板使用
return {
msg,
log
}
}
})
</script>
<template>
<button @click="log">{{ msg }}</button>
</template>相对于普通的 <script>,<script setup> 的差异主要在于:
- 样板代码更少。
- props 和自定义事件可以直接用 TypeScript 声明。
- 模板会被编译到同一作用域,减少渲染上下文代理开销。
- IDE 类型推导路径更直接。
Teleport
Teleport 官方定义如下:
<Teleport>是一个内置组件,可将组件内部的一部分模板“传送”到该组件 DOM 结构外层的位置。
典型场景
- 弹窗(modal)
- 全屏遮罩层(overlay)
- 悬浮菜单(dropdown)
- tooltip、通知栏等不希望被 CSS 继承/干扰的元素
Teleport 处理的问题是:逻辑代码和模板虽然位于同一组件中,但目标 DOM 可能需要脱离当前层级,否则容易受到上层样式和布局上下文影响。
Teleport 允许目标 DOM 直接渲染到指定外层容器,减少样式污染和层级限制。
Teleport 只改变 DOM 渲染位置,不改变组件逻辑关系。
运行时机制:
被 Teleport 包裹的 DOM 会被打上特殊标记:
js
export const TELEPORT: unique symbol = Symbol(__DEV__ ? `Teleport` : ``)运行时会生成特殊 VNode,并在 patch 阶段(虚拟 DOM 转真实 DOM)识别为 TELEPORT 后走专门分支处理。
Suspense
<Suspense> 是 Vue3 新增的内置组件,用于在组件树中协调异步依赖处理。它允许上层等待下层多个嵌套异步依赖解析完成,并在等待阶段渲染加载状态。
渲染流程核心步骤
- 遇到
<Suspense>标签
- 编译器将其编译成 vnode,type 是内置组件
SuspenseImpl - 其子插槽
default和fallback分别作为slots.default和slots.fallback
- 执行 default 插槽
- Vue 会尝试执行 default 插槽中的组件 setup()
- 如果这些组件中的 setup() 返回了 Promise(或 await 表达式),Vue 会将它们加入等待队列
suspense.asyncDeps
- 渲染 fallback
- 当
suspense.asyncDeps > 0,Vue 会先渲染 fallback 插槽内容 - fallback 是实际插入 DOM 的元素(如
<Loading />)
- 等待异步内容完成
- 每个异步组件 resolve 后会调用
suspense.depsResolved(),减少计数 - 所有 asyncDeps 全部完成后,Vue 会:
- 卸载 fallback 插槽
- 挂载 default 插槽的实际渲染内容(即真实组件内容)
Fragment
Vue2 要求 template 具有单一根节点;Vue3 允许多个根节点,并在运行时将其组织为 Fragment 结构。
响应系统重构
Vue 3 使用基于 Proxy 的响应式系统替代 Vue 2 中的 Object.defineProperty 实现,响应式核心因此支持了更完整的对象操作拦截。
核心变化:基于 Proxy 构建响应式对象
| Vue 版本 | 响应式实现方式 |
|---|---|
| Vue 2 | Object.defineProperty(递归劫持,难以监听新增/删除) |
| Vue 3 | Proxy(动态劫持,无需递归,支持新增/删除) |
核心 API 简介
| API 名称 | 作用描述 |
|---|---|
reactive() | 将对象变为响应式对象(深层) |
ref() | 创建响应式的基本类型(如字符串、数字等) |
computed() | 创建计算属性,自动依赖收集 |
watch() | 响应式侦听器,监听值的变化 |
effect() | Vue 内部依赖追踪的原始 API(不常用) |
