Skip to content

Vue 2 实例 API 与全局 API

大白话解释: Vue 2 的 API 就像"工具箱里的工具"。有些工具是全局的(Vue.componentVue.directive),有些是实例上的(this.$refsthis.$emitthis.$nextTick)。

为什么要了解这些 API?

  • 日常开发必备$refs 获取 DOM、$emit 触发事件、$nextTick 等待 DOM 更新
  • 排查问题$data 查看数据、$watch 监听变化、$set 强制更新
  • 高级用法Vue.extend 动态创建组件(弹窗、通知)、Vue.observable 创建响应式对象

常用实例 API:

  • $refs:获取 DOM 元素或子组件引用
  • $emit:触发父组件监听的事件
  • $nextTick:等待 DOM 更新后执行
  • $set:强制设置响应式属性(Vue 2 新增属性时必须用)
  • $delete:强制删除响应式属性

Vue 2 实例属性/方法和全局 API 的详细用法。


全局 API

Vue.extend —— 创建子类

⚠️ Vue 3 已移除。Vue 3 替代方案:

  • 创建应用:createApp(App).mount('#app')
  • 动态创建组件:createVNode + render(来自 @vue/runtime-dom
  • 类型推导:defineComponent()(仅用于类型辅助,不能 new 实例化)

基于一个组件选项对象,返回一个可复用的构造函数。

js
import Vue from 'vue'

// 创建构造器
const Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}}</p>',
  data() {
    return {
      firstName: '张',
      lastName: '三'
    }
  }
})

// 挂载到 DOM
new Profile().$mount('#app')

// 挂载到指定元素(返回组件实例)
const instance = new Profile().$mount()
document.getElementById('app').appendChild(instance.$el)

动态创建组件实例(弹窗/通知)

js
import Vue from 'vue'
import ConfirmDialog from './ConfirmDialog.vue'

function showConfirm(message) {
  const Confirm = Vue.extend(ConfirmDialog)
  const instance = new Confirm({
    propsData: { message }    // 传入 props
  }).$mount()

  // 插入 DOM
  document.body.appendChild(instance.$el)

  // 监听事件
  instance.$on('confirm', () => {
    console.log('用户确认')
    instance.$destroy()
    instance.$el.remove()
  })

  instance.$on('cancel', () => {
    instance.$destroy()
    instance.$el.remove()
  })
}

// 使用
showConfirm('确定要删除吗?')

Vue.component —— 注册全局组件

⚠️ Vue 3 改为 app.component()。Vue 3 通过 createApp() 创建应用实例后,使用 app.component('name', component) 注册全局组件,不再挂载在 Vue 构造函数上。

js
// Vue 2 写法
import Vue from 'vue'

// 注册全局组件(在任何模板中都可使用)
Vue.component('my-button', {
  template: '<button @click="count++">{{ count }}</button>',
  data() {
    return { count: 0 }
  }
})

// 注册时使用 .vue 文件
Vue.component('MyButton', MyButton)

💡 推荐用局部注册(components 选项),按需加载,减少打包体积。


Vue.directive —— 注册全局指令

⚠️ Vue 3 改为 app.directive()。注册方式从 Vue.directive('name', options) 变为 app.directive('name', options)

js
import Vue from 'vue'

// 注册全局自定义指令
Vue.directive('focus', {
  // 元素插入 DOM 时自动聚焦
  inserted(el) {
    el.focus()
  }
})

// 使用
// <input v-focus>

完整钩子函数

⚠️ Vue 3 指令钩子已更名bindbeforeMountinsertedmountedupdateupdatedcomponentUpdated 已移除,unbindunmounted。详见 Vue 3 自定义指令文档

js
// Vue 2 指令钩子(Vue 3 钩子名称见上方说明)
Vue.directive('my-directive', {
  bind(el, binding, vnode) {           // Vue 3: beforeMount
    // 指令第一次绑定到元素时调用(只执行一次)
    // el: 元素, binding: 指令信息, vnode: VNode
    console.log(binding.value)      // v-my-directive="value" 中的 value
    console.log(binding.arg)        // v-my-directive:arg 中的 arg
    console.log(binding.modifiers)  // v-my-directive.modifier 中的 modifiers
  },
  inserted(el, binding) {             // Vue 3: mounted
    // 被插入父节点时调用(保证父节点存在)
  },
  update(el, binding, vnode, oldVnode) {  // Vue 3: updated
    // 所在组件的 VNode 更新时调用
  },
  componentUpdated(el, binding, vnode, oldVnode) {  // Vue 3 已移除
    // 所在组件的 VNode 及其子 VNode 全部更新后调用
  },
  unbind(el, binding) {               // Vue 3: unmounted
    // 指令与元素解绑时调用(只执行一次)
  }
})

实用示例:v-permission 权限指令

js
Vue.directive('permission', {
  inserted(el, binding) {
    const requiredRoles = binding.value   // ['admin', 'editor']
    const userRole = store.state.user.role

    if (!requiredRoles.includes(userRole)) {
      el.parentNode && el.parentNode.removeChild(el)
    }
  }
})

// 使用
// <button v-permission="['admin']">删除</button>
// <button v-permission="['admin', 'editor']">编辑</button>

实用示例:v-click-outside 点击外部

js
Vue.directive('click-outside', {
  bind(el, binding) {
    el._clickOutside = (e) => {
      if (!el.contains(e.target)) {
        binding.value(e)    // 执行回调
      }
    }
    document.addEventListener('click', el._clickOutside)
  },
  unbind(el) {
    document.removeEventListener('click', el._clickOutside)
    delete el._clickOutside
  }
})

// 使用
// <div v-click-outside="closeDropdown">...</div>

Vue.filter —— 注册全局过滤器

js
import Vue from 'vue'

// 全局过滤器
Vue.filter('dateFormat', (value, format = 'YYYY-MM-DD') => {
  if (!value) return ''
  return dayjs(value).format(format)
})

Vue.filter('currency', (value, symbol = '¥') => {
  return `${symbol}${Number(value).toFixed(2)}`
})

// 使用(模板中)
// {{ date | dateFormat('YYYY/MM/DD') }}
// {{ price | currency }}
// {{ price | currency('$') }}
js
// 局部过滤器
export default {
  filters: {
    uppercase(value) {
      return value.toUpperCase()
    }
  }
}
// 使用:{{ text | uppercase }}

⚠️ 过滤器是 Vue 2 独有,Vue 3 已移除。推荐用 computed 或 methods 替代。


Vue.mixin —— 全局混入

js
import Vue from 'vue'

// 全局混入:影响所有组件(慎用)
Vue.mixin({
  created() {
    const myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

// 所有组件都会执行这段 created 钩子

⚠️ 全局混入会影响每个组件,推荐用局部混入(mixins 选项)替代。

局部混入

js
// mixins/logger.js
export const loggerMixin = {
  created() {
    console.log(`[${this.$options.name}] 已创建`)
  },
  methods: {
    log(msg) {
      console.log(`[${this.$options.name}]`, msg)
    }
  }
}

// 组件中使用
import { loggerMixin } from '@/mixins/logger'

export default {
  name: 'UserList',
  mixins: [loggerMixin],
  created() {
    this.log('初始化完成')  // [UserList] 初始化完成
  }
}

Vue.use —— 安装插件

js
import Vue from 'vue'
import ElementUI from 'element-ui'
import VueRouter from 'vue-router'
import Vuex from 'vuex'

// 安装插件(插件必须有 install 方法,或本身是函数)
Vue.use(ElementUI)
Vue.use(VueRouter)
Vue.use(Vuex)

自定义插件

js
// plugins/my-plugin.js
const MyPlugin = {
  install(Vue, options) {
    // 1. 添加全局方法
    Vue.myGlobalMethod = () => { console.log('全局方法') }

    // 2. 添加全局指令
    Vue.directive('my-directive', {
      bind(el, binding) { /* ... */ }
    })

    // 3. 添加全局过滤器
    Vue.filter('my-filter', (value) => value.toUpperCase())

    // 4. 添加实例方法(通过原型)
    Vue.prototype.$myMethod = (msg) => {
      console.log(msg)
    }
  }
}

// 使用
Vue.use(MyPlugin, { /* 选项 */ })

Vue.observable —— 创建响应式对象

⚠️ Vue 3 已移除。Vue 3 直接使用 reactive()ref()(从 vue 导入)创建响应式对象:

ts
import { reactive } from 'vue'
const store = reactive({ count: 0, name: '张三' })
js
import Vue from 'vue'

// 创建跨组件共享的响应式对象(简易状态管理)
const store = Vue.observable({
  count: 0,
  name: '张三'
})

// 在组件中使用
export default {
  computed: {
    count: () => store.count,
    name: () => store.name
  },
  methods: {
    increment() {
      store.count++    // 直接修改,视图自动更新
    }
  }
}

💡 Vue 2.6+ 可用。适合小型项目替代 Vuex。


Vue.set / Vue.delete

js
import Vue from 'vue'

// Vue.set —— 新增响应式属性
const vm = new Vue({
  data: { user: { name: '张三' } }
})

// ❌ 直接添加属性不是响应式的
vm.user.age = 25

// ✅ Vue.set 使新属性是响应式的
Vue.set(vm.user, 'age', 25)
// 或
this.$set(vm.user, 'age', 25)

// Vue.set —— 数组下标赋值
Vue.set(vm.items, 0, 'new')
// 或
this.$set(vm.items, 0, 'new')

// Vue.delete —— 删除响应式属性
Vue.delete(vm.user, 'age')
// 或
this.$delete(vm.user, 'age')

Vue.nextTick

js
import Vue from 'vue'

// DOM 更新后执行回调
Vue.nextTick(() => {
  // DOM 已更新
})

// 实例方法(更常用)
this.$nextTick(() => {
  // DOM 已更新
})

// async/await 写法
await this.$nextTick()
// DOM 已更新

实例属性

$data —— 响应式数据

js
this.$data           // 返回 data 选项的对象引用
this.$data.count     // 等同于 this.count

$props —— 当前 Props

js
this.$props          // 当前组件接收到的 props(只读)

$el —— 根 DOM 元素

js
this.$el             // 组件对应的 DOM 元素
this.$el.style.color = 'red'

// 在 created 中不可用(DOM 还未挂载)
// 在 mounted 中可用

$refs —— DOM/组件引用

vue
<template>
  <input ref="input" />
  <MyForm ref="form" />
</template>

<script>
export default {
  mounted() {
    // 访问 DOM 元素
    this.$refs.input.focus()

    // 访问子组件
    this.$refs.form.validate()
  }
}
</script>

$parent / $children / $root

js
// $parent:访问父组件实例(尽量避免使用,会增加父子组件的耦合度)
this.$parent.someMethod()
this.$parent.someData

// $children:子组件实例数组(非响应式,顺序不保证,不推荐使用)
this.$children[0].someMethod()

// $root:根组件实例(整个应用最顶层的组件)
// 适合场景:小型项目中跨组件共享数据(替代 Vuex)
this.$root.someData

$slots / $scopedSlots

js
// $slots:获取插槽内容(VNode 数组)
this.$slots          // 所有插槽(包括默认插槽)
this.$slots.header   // 获取 name="header" 的具名插槽内容

// $scopedSlots:获取作用域插槽(Vue 2.6+ 推荐用 v-slot 语法)
// 适合场景:封装组件时,需要在 JS 中操作插槽内容
this.$scopedSlots

⚠️ Vue 3 已移除 $scopedSlots。Vue 3 统一了插槽 API,所有插槽(包括作用域插槽)都通过 $slots 访问,且以函数形式暴露(如 $slots.default?.())。

$attrs / $listeners

js
// $attrs: 未被 props 声明的属性(class/style 除外)
// 用于跨组件透传
this.$attrs

// $listeners: 父组件绑定的事件监听器
// 用于跨组件透传
this.$listeners

实例方法

$emit —— 触发事件

js
// 触发自定义事件
this.$emit('change', newValue)
this.$emit('update:value', newValue)

// 模板中监听
// <MyComp @change="handleChange" />

$on / $once / $off —— 事件监听

js
// 监听自定义事件(配合 $emit 使用)
this.$on('event-name', (data) => {
  console.log(data)
})

// 只监听一次
this.$once('event-name', (data) => {
  console.log(data)
})

// 取消监听
this.$off('event-name', handler)   // 取消指定事件的指定回调
this.$off('event-name')            // 取消指定事件的所有回调
this.$off()                        // 取消所有事件的所有回调

⚠️ $on / $off 只能监听自己 $emit 触发的事件,不能跨组件通信。跨组件用 EventBus。

$watch —— 手动监听

js
// 监听数据变化
const unwatch = this.$watch('count', (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`)
})

// 取消监听(调用返回的函数)
unwatch()

// 深度监听
this.$watch('user', (newVal) => {
  console.log(newVal)
}, { deep: true })

// 立即执行一次
this.$watch('count', (newVal) => {
  console.log(newVal)
}, { immediate: true })

// 监听表达式(函数形式)
this.$watch(
  () => this.user.name,
  (newVal) => { console.log(newVal) }
)

$set / $delete —— 响应式操作

js
// $set —— 新增响应式属性
this.$set(this.user, 'age', 25)
this.$set(this.items, 0, 'new')

// $delete —— 删除响应式属性
this.$delete(this.user, 'age')

$forceUpdate —— 强制更新

js
// 强制组件重新渲染(不常用,一般不需要)
this.$forceUpdate()

// 典型场景:修改了非响应式数据,需要手动触发更新
this.$forceUpdate()

⚠️ 大多数情况下不需要。如果需要,说明数据不是响应式的,应从根源解决。

$nextTick —— DOM 更新后执行

js
// 修改数据后,DOM 不会立即更新
this.count = 1
console.log(this.$el.textContent)  // 旧值

// $nextTick 在 DOM 更新后执行
this.count = 1
this.$nextTick(() => {
  console.log(this.$el.textContent)  // 新值
})

// async/await
this.count = 1
await this.$nextTick()
console.log(this.$el.textContent)  // 新值

$destroy —— 销毁实例

js
// 销毁组件实例
this.$destroy()

// 销毁后清理事件监听、子组件、watcher
// 不会移除 DOM,需要手动 this.$el.remove()

⚠️ 一般不需要手动调用。v-if 切换或路由切换会自动销毁。


参考

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