Skip to content

Vue 3 应用 API 与组合式工具

大白话解释: Vue 3 的 API 分为几类:

  • 应用实例 API:创建应用、注册全局组件/指令/插件
  • 组合式 APIrefreactivecomputedwatch 等响应式工具
  • 内置组件KeepAliveTeleportSuspense

Vue 3 vs Vue 2 API 的区别:

  • 全局 API → 应用实例 APIVue.componentapp.component,避免全局污染
  • Options API → Composition API:更灵活的代码组织方式
  • 新增工具provide/injectuseCssModuleuseSlots

常用组合式工具:

  • ref / reactive:创建响应式数据
  • computed:计算属性
  • watch / watchEffect:监听数据变化
  • provide / inject:跨层级传值
  • useSlots / useAttrs:访问插槽和属性

Vue 3 应用实例 API、全局 API 和组合式函数工具的详细用法。


应用实例(app)

createApp —— 创建应用

ts
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')    // 挂载到 DOM

app.component —— 注册全局组件

ts
// 注册单个全局组件
app.component('MyButton', MyButton)  // 注册全局组件,所有组件可用

// 获取已注册的组件
const MyButton = app.component('MyButton')  // 获取已注册的组件

💡 推荐局部注册,按需加载减少体积。


app.directive —— 注册全局指令

ts
// 注册指令(Vue 3 钩子函数名有变化)
app.directive('focus', {
  mounted(el) {  // 元素挂载后执行
    el.focus()   // 自动聚焦
  }
})

// 使用:<input v-focus>

Vue 3 指令钩子

ts
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 的 bindbeforeMountinsertedmountedunbindunmounted

实用示例:v-permission

ts
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

ts
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 防抖

ts
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 —— 全局混入

ts
// 全局混入(影响所有组件,慎用)
app.mixin({
  created() {
    // 每个组件都会执行,谨慎使用
  }
})

⚠️ Vue 3 不推荐使用全局混入,用 composables 替代:

ts
// composables/useLogger.ts
export function useLogger(name: string) {
  onMounted(() => console.log(`[${name}] 已挂载`))
}

app.provide / inject —— 全局注入

ts
// 应用级 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 —— 安装插件

ts
// 插件可以是对象(有 install 方法)或函数
app.use(router)      // 安装路由插件
app.use(pinia)       // 安装状态管理插件
app.use(ElementPlus) // 安装 UI 组件库
app.use(MyPlugin, { /* 选项 */ })  // 安装自定义插件

自定义插件

ts
// 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 —— 全局属性

ts
// 挂载全局方法/属性(替代 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 —— 应用配置

ts
// 自定义错误处理(全局捕获组件错误)
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

ts
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 —— 类型推导

ts
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 —— 异步组件

ts
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

ts
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

ts
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

ts
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(浅层)响应式?

正常情况下,refreactive 会深度追踪对象的每一层变化。这意味着如果你有一个嵌套很深的对象,Vue 会递归地把每一层都变成响应式的,这会消耗很多性能。

shallowRefshallowReactive 只追踪"浅层"的变化:

  • shallowRef:只追踪 .value 的引用变化,不深度追踪内部属性
  • shallowReactive:只追踪顶层属性,不深度追踪嵌套对象

什么时候用?

  1. 大型只读数据:比如 ECharts 配置对象、大型表格数据,数据很大但不需要频繁修改内部结构
  2. 第三方库实例:某些第三方库的对象不适合被 Vue 代理(可能破坏内部状态)
  3. 性能优化:当数据量很大且确定只需要替换整个对象而非修改内部属性时

性能对比:

  • ref({ list: [1, 2, 3] }):Vue 会递归追踪 list 数组的每一项变化
  • shallowRef({ list: [1, 2, 3] }):Vue 只追踪 .value 是否被替换,不追踪 list 内部变化
ts
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

ts
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

ts
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 / provide

useSlots / useAttrs

vue
<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 —— 运行时声明:

vue
<script setup>
const props = defineProps({
  title: { type: String, required: true },  // 标题,必填
  count: { type: Number, default: 0 }       // 计数,默认 0
})
</script>

defineProps —— 类型声明(推荐):

vue
<script setup lang="ts">
const props = defineProps<{
  title: string      // 标题
  count?: number     // 可选计数
}>()
</script>

defineEmits —— 运行时声明:

vue
<script setup>
const emit = defineEmits(['update', 'delete'])  // 声明事件名
</script>

defineEmits —— 类型声明(推荐):

vue
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'update', value: string): void  // update 事件
  (e: 'delete', id: number): void     // delete 事件
}>()
</script>

defineExpose / defineModel / defineOptions:

vue
<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

ts
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 2Vue 3说明
new Vue()createApp()创建应用
Vue.component()app.component()注册全局组件
Vue.directive()app.directive()注册全局指令
Vue.mixin()app.mixin()全局混入(不推荐)
Vue.use()app.use()安装插件
Vue.prototype.$xapp.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

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准