Vue 3 应用 API 与组合式工具
大白话解释: Vue 3 的 API 分为几类:
- 应用实例 API:创建应用、注册全局组件/指令/插件
- 组合式 API:
ref、reactive、computed、watch等响应式工具 - 内置组件:
KeepAlive、Teleport、Suspense等
Vue 3 vs Vue 2 API 的区别:
- 全局 API → 应用实例 API:
Vue.component→app.component,避免全局污染 - Options API → Composition API:更灵活的代码组织方式
- 新增工具:
provide/inject、useCssModule、useSlots等
常用组合式工具:
ref/reactive:创建响应式数据computed:计算属性watch/watchEffect:监听数据变化provide/inject:跨层级传值useSlots/useAttrs:访问插槽和属性
Vue 3 应用实例 API、全局 API 和组合式函数工具的详细用法。
应用实例(app)
createApp —— 创建应用
import { createApp } from 'vue' // 导入 createApp
import App from './App.vue' // 导入根组件
const app = createApp(App) // 创建应用实例
// 挂载到 DOM 元素
app.mount('#app')
// 链式写法:安装插件并挂载
createApp(App)
.use(router) // 安装路由插件
.use(pinia) // 安装状态管理插件
.use(ElementPlus) // 安装 UI 组件库
.mount('#app') // 挂载到 DOMapp.component —— 注册全局组件
// 注册单个全局组件
app.component('MyButton', MyButton) // 注册全局组件,所有组件可用
// 获取已注册的组件
const MyButton = app.component('MyButton') // 获取已注册的组件💡 推荐局部注册,按需加载减少体积。
app.directive —— 注册全局指令
// 注册指令(Vue 3 钩子函数名有变化)
app.directive('focus', {
mounted(el) { // 元素挂载后执行
el.focus() // 自动聚焦
}
})
// 使用:<input v-focus>Vue 3 指令钩子
app.directive('my-directive', {
created(el, binding, vnode, prevVnode) {}, // 元素创建时(属性还未应用)
beforeMount(el, binding) {}, // 挂载前
mounted(el, binding) {}, // 挂载后
beforeUpdate(el, binding, vnode, prevVnode) {}, // 更新前
updated(el, binding, vnode, prevVnode) {}, // 更新后
beforeUnmount(el, binding) {}, // 卸载前
unmounted(el, binding) {} // 卸载后
})💡 Vue 3 指令钩子名称与组件生命周期一致,Vue 2 的
bind→beforeMount,inserted→mounted,unbind→unmounted。
实用示例:v-permission
app.directive('permission', {
mounted(el, binding) {
const roles = binding.value // 获取指令值,如 ['admin']
const userRole = useUserStore().role // 获取当前用户角色
if (!roles.includes(userRole)) {
el.remove() // 移除元素,无权限
}
}
})
// <button v-permission="['admin']">删除</button>实用示例:v-click-outside
app.directive('click-outside', {
mounted(el, binding) {
// 保存事件处理函数,便于卸载时移除
el._clickOutside = (e) => {
if (!el.contains(e.target)) binding.value(e) // 点击元素外部时执行回调
}
document.addEventListener('click', el._clickOutside) // 添加全局点击监听
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside) // 移除监听,避免内存泄漏
}
})
// <div v-click-outside="close">...</div>实用示例:v-debounce 防抖
app.directive('debounce', {
mounted(el, binding) {
const { value, arg = 'click', modifiers } = binding // 解构指令参数
const delay = parseInt(Object.keys(modifiers)[0]) || 300 // 获取延迟时间,默认 300ms
let timer // 定时器引用
el.addEventListener(arg, (...args) => {
clearTimeout(timer) // 清除之前的定时器
timer = setTimeout(() => value(...args), delay) // 延迟执行回调
})
}
})
// <button v-debounce.500="handleSubmit">提交</button>app.mixin —— 全局混入
// 全局混入(影响所有组件,慎用)
app.mixin({
created() {
// 每个组件都会执行,谨慎使用
}
})⚠️ Vue 3 不推荐使用全局混入,用 composables 替代:
ts// composables/useLogger.ts export function useLogger(name: string) { onMounted(() => console.log(`[${name}] 已挂载`)) }
app.provide / inject —— 全局注入
// 应用级 provide(所有组件都可 inject)
app.provide('appName', 'My App') // 提供应用名称
app.provide('apiUrl', 'https://api.example.com') // 提供 API 地址
// 组件中使用
const appName = inject('appName') // 注入应用名称
const apiUrl = inject('apiUrl') // 注入 API 地址app.use —— 安装插件
// 插件可以是对象(有 install 方法)或函数
app.use(router) // 安装路由插件
app.use(pinia) // 安装状态管理插件
app.use(ElementPlus) // 安装 UI 组件库
app.use(MyPlugin, { /* 选项 */ }) // 安装自定义插件自定义插件
// plugins/my-plugin.js
export const MyPlugin = {
install(app, options) {
// 1. 全局属性
app.config.globalProperties.$myUtil = (msg) => console.log(msg)
// 2. 全局组件
app.component('MyButton', MyButton)
// 3. 全局指令
app.directive('focus', { mounted(el) { el.focus() } })
// 4. 全局 provide
app.provide('myKey', options.value)
}
}
// 使用
app.use(MyPlugin, { value: 'hello' }) // 安装插件,传递选项app.config.globalProperties —— 全局属性
// 挂载全局方法/属性(替代 Vue 2 的 Vue.prototype)
app.config.globalProperties.$http = axios // 挂载 HTTP 客户端
app.config.globalProperties.$filters = {
dateFormat(value) { return dayjs(value).format('YYYY-MM-DD') } // 日期格式化
}
// 组件中使用(Options API)
this.$http.get('/api/user') // 发起请求
this.$filters.dateFormat(date) // 格式化日期⚠️ 注意:
this.$http在 Composition API 的<script setup>中无法使用,因为无法访问this。推荐直接import axios from 'axios'或使用inject。
💡 Composition API 的
<script setup>中无法访问this,用 inject 或直接 import 替代。
app.config —— 应用配置
// 自定义错误处理(全局捕获组件错误)
app.config.errorHandler = (err, instance, info) => {
console.error('组件错误:', err) // 输出错误
console.error('错误信息:', info) // 输出错误详情
// 上报到监控平台
}
// 自定义警告处理
app.config.warnHandler = (msg, instance, trace) => {
console.warn('警告:', msg) // 输出警告
}
// 开启性能追踪
app.config.performance = true // 开发环境性能分析h() —— 手动创建 VNode
import { h } from 'vue' // 导入 h 函数
// 基本用法:创建 VNode
const vnode1 = h('div', { class: 'container' }, 'Hello') // 元素 + 属性 + 文本
// 带属性
const vnode2 = h('div', {
class: 'box', // CSS 类名
style: { color: 'red' }, // 内联样式
onClick: () => console.log('clicked') // 事件监听
}, '内容')
// 带子节点
const vnode3 = h('div', [
h('h1', '标题'), // 子元素
h('p', '段落') // 子元素
])
// 渲染函数组件中使用
export default {
setup() {
return () => h('div', { class: 'container' }, [
h('h1', '标题'),
h('p', `当前时间: ${new Date().toLocaleString()}`)
])
}
}defineComponent —— 类型推导
import { defineComponent } from 'vue' // 导入 defineComponent
// 用于 Options API 的类型推导
export default defineComponent({
props: {
title: { type: String, required: true } // 标题 prop
},
data() {
return { count: 0 } // 响应式数据
},
computed: {
double() { return this.count * 2 } // 有完整类型推导
}
})💡
<script setup>不需要 defineComponent,直接用defineProps/defineEmits。
defineAsyncComponent —— 异步组件
import { defineAsyncComponent } from 'vue' // 导入 defineAsyncComponent
// 基本用法:动态导入组件
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
// 带选项:配置加载状态和错误处理
const HeavyChart = defineAsyncComponent({
loader: () => import('./HeavyChart.vue'), // 动态导入函数
loadingComponent: LoadingSpinner, // 加载中显示的组件
errorComponent: ErrorDisplay, // 加载失败显示的组件
delay: 200, // 延迟多久显示 loading(默认 200ms)
timeout: 10000, // 超时时间(默认 Infinity)
suspensible: false, // 是否触发 Suspense
onError(error, retry, fail, attempts) {
if (attempts <= 3) retry() // 重试 3 次
else fail() // 超过次数失败
}
})组合式工具函数
toRef / toRefs
import { reactive, toRef, toRefs } from 'vue' // 导入 reactive、toRef、toRefs
const state = reactive({ name: '张三', age: 25 }) // 创建响应式对象
// toRef —— 将 reactive 的单个属性转为 ref(保持响应式连接)
const nameRef = toRef(state, 'name') // 将 state.name 转为 ref
nameRef.value = '李四' // state.name 也会变
// toRefs —— 将 reactive 的所有属性转为 ref(解构时保持响应式)
const { name, age } = toRefs(state) // 解构所有属性
name.value = '王五' // state.name 也会变
// 典型场景:composable 返回值
function useUser() {
const state = reactive({ name: '', age: 0 }) // 创建响应式状态
// ... 获取数据
return { ...toRefs(state) } // 解构后仍保持响应式
}toRaw / markRaw
import { reactive, toRaw, markRaw } from 'vue' // 导入 toRaw 和 markRaw
// toRaw —— 获取响应式对象的原始对象(绕过代理)
const state = reactive({ name: '张三' }) // 创建响应式对象
const raw = toRaw(state) // 获取原始对象,修改不会触发更新
raw.name = '李四' // 不触发响应式
// markRaw —— 标记对象永远不被转为响应式
const chart = markRaw(new ECharts(dom)) // 标记为非代理
const chartState = reactive({ chart }) // chart 不会被代理unref / isRef / isReactive / isReadonly / isProxy
import { ref, reactive, readonly, unref, isRef, isReactive, isReadonly, isProxy } from 'vue' // 导入工具函数
// unref —— 如果是 ref 则返回 .value,否则返回自身
const myRef = ref('hello') // 创建 ref
unref(myRef) // 'hello'(等价于 isRef(val) ? val.value : val)
// 类型判断
isRef(ref(1)) // true,是 ref
isReactive(reactive({})) // true,是 reactive
isReadonly(readonly({})) // true,是 readonly
isProxy(reactive({})) // true(reactive 和 readonly 都是 proxy)shallowRef / shallowReactive
什么是 shallow(浅层)响应式?
正常情况下,ref 和 reactive 会深度追踪对象的每一层变化。这意味着如果你有一个嵌套很深的对象,Vue 会递归地把每一层都变成响应式的,这会消耗很多性能。
shallowRef 和 shallowReactive 只追踪"浅层"的变化:
shallowRef:只追踪.value的引用变化,不深度追踪内部属性shallowReactive:只追踪顶层属性,不深度追踪嵌套对象
什么时候用?
- 大型只读数据:比如 ECharts 配置对象、大型表格数据,数据很大但不需要频繁修改内部结构
- 第三方库实例:某些第三方库的对象不适合被 Vue 代理(可能破坏内部状态)
- 性能优化:当数据量很大且确定只需要替换整个对象而非修改内部属性时
性能对比:
ref({ list: [1, 2, 3] }):Vue 会递归追踪 list 数组的每一项变化shallowRef({ list: [1, 2, 3] }):Vue 只追踪.value是否被替换,不追踪 list 内部变化
import { shallowRef, shallowReactive, triggerRef } from 'vue' // 导入浅层响应式 API
// shallowRef —— 只追踪 .value 的引用变化,不深度追踪
const data = shallowRef({ list: [1, 2, 3] }) // 创建浅层 ref
data.value.list.push(4) // ❌ 不触发更新,修改内部属性
data.value = { list: [1, 2, 3, 4] } // ✅ 替换引用才触发
// 手动触发:修改内部属性后手动触发更新
data.value.list.push(4) // 修改内部属性
triggerRef(data) // ✅ 手动触发更新
// shallowReactive —— 只追踪顶层属性,不深度追踪
const state = shallowReactive({ user: { name: '张三' } }) // 创建浅层 reactive
state.user.name = '李四' // ❌ 不触发更新(嵌套属性)
state.user = { name: '李四' } // ✅ 顶层属性变化触发readonly / shallowReadonly
import { reactive, readonly, shallowReadonly } from 'vue' // 导入只读 API
const state = reactive({ count: 0, user: { name: '张三' } }) // 创建响应式对象
const shallowReadOnly = shallowReadonly(state) // 创建浅层只读代理
shallowReadOnly.count = 1 // ✅ 顶层属性只读,会警告
shallowReadOnly.user.name = '李四' // ✅ 嵌套属性可修改组合式 API 中的内置函数
getCurrentInstance
import { getCurrentInstance } from 'vue' // 导入 getCurrentInstance
// 获取当前组件实例(仅在 setup 中可用)
const instance = getCurrentInstance()
if (!instance) {
throw new Error('getCurrentInstance() 必须在 setup 中使用')
}
const { proxy, ctx, emit, props, slots } = instance
// 常见用途:访问全局属性
const { proxy } = getCurrentInstance()!
proxy!.$http.get('/api/user') // 访问 globalProperties
// ⚠️ 不推荐在生产代码中依赖,优先用 inject / provideuseSlots / useAttrs
<script setup>
import { useSlots, useAttrs } from 'vue' // 导入 useSlots 和 useAttrs
const slots = useSlots() // 访问插槽
const attrs = useAttrs() // 访问透传属性(非 props 的属性)
// 检查是否有默认插槽
if (slots.default) {
console.log('有默认插槽')
}
// 获取父组件传入的非 props 属性
console.log(attrs.class) // 获取 class 属性
console.log(attrs.onClick) // 获取点击事件
</script>defineProps / defineEmits / defineExpose / defineModel / defineOptions
defineProps —— 运行时声明:
<script setup>
const props = defineProps({
title: { type: String, required: true }, // 标题,必填
count: { type: Number, default: 0 } // 计数,默认 0
})
</script>defineProps —— 类型声明(推荐):
<script setup lang="ts">
const props = defineProps<{
title: string // 标题
count?: number // 可选计数
}>()
</script>defineEmits —— 运行时声明:
<script setup>
const emit = defineEmits(['update', 'delete']) // 声明事件名
</script>defineEmits —— 类型声明(推荐):
<script setup lang="ts">
const emit = defineEmits<{
(e: 'update', value: string): void // update 事件
(e: 'delete', id: number): void // delete 事件
}>()
</script>defineExpose / defineModel / defineOptions:
<script setup>
// defineExpose —— 暴露属性给父组件(ref 访问)
defineExpose({ count, name }) // 暴露 count 和 name
// defineModel —— v-model 简化(Vue 3.4+)
const modelValue = defineModel() // v-model
const title = defineModel('title') // v-model:title
// defineOptions —— 定义额外选项(Vue 3.3+)
defineOptions({ name: 'MyComponent', inheritAttrs: false }) // 组件名和属性继承
</script>nextTick
import { nextTick } from 'vue' // 导入 nextTick
// Vue 3 直接导入使用(不再需要 this.$nextTick)
count.value = 1 // 修改响应式数据
await nextTick() // 等待 DOM 更新
// DOM 已更新
// Vue 2 对比:this.$nextTick(() => {})Vue 2 vs Vue 3 API 对照
| Vue 2 | Vue 3 | 说明 |
|---|---|---|
new Vue() | createApp() | 创建应用 |
Vue.component() | app.component() | 注册全局组件 |
Vue.directive() | app.directive() | 注册全局指令 |
Vue.mixin() | app.mixin() | 全局混入(不推荐) |
Vue.use() | app.use() | 安装插件 |
Vue.prototype.$x | app.config.globalProperties.$x | 全局属性 |
Vue.filter() | ❌ 已移除 | 用 computed/methods 替代 |
Vue.observable() | reactive() | 创建响应式对象 |
Vue.set() | 不需要 | Proxy 自动检测 |
Vue.delete() | 不需要 | Proxy 自动检测 |
Vue.nextTick() | nextTick() | 直接导入使用 |
Vue.extend() | defineComponent() | 类型推导辅助(不能 new 实例化,动态创建用 createApp) |
this.$on/$off | ❌ 已移除 | 用 mitt 等第三方库 |
this.$children | ❌ 已移除 | 用 ref 访问子组件 |
this.$listeners | ❌ 已移除 | 合并到 $attrs |
this.$scopedSlots | ❌ 已移除 | 统一为 $slots |