目录
[1、 依赖注入是什么?](#1、 依赖注入是什么?)
[4、 使用 Symbol 作注入名](#4、 使用 Symbol 作注入名)
[4、配置加载状态:加载提示 / 超时 / 错误处理](#4、配置加载状态:加载提示 / 超时 / 错误处理)
[3、 各生命周期钩子的详细解释](#3、 各生命周期钩子的详细解释)
[1. setup()](#1. setup())
[2. onBeforeMount()](#2. onBeforeMount())
[3. onMounted()](#3. onMounted())
[4. onBeforeUpdate()](#4. onBeforeUpdate())
[6. onBeforeUnmount()](#6. onBeforeUnmount())
[7. onUnmounted()](#7. onUnmounted())
[1、什么是 Vue 插件?](#1、什么是 Vue 插件?)
一、依赖注入
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。想象一下这样的结构:有一些多层级嵌套的组件,形成了一棵巨大的组件树,而某个深层的子组件需要一个较远的祖先组件中的部分数据。在这种情况下,如果仅使用 props 则必须将其沿着组件链逐级传递下去,这会非常麻烦:
1、 依赖注入是什么?
依赖注入是一种让"祖先组件提供 "和"后代组件注入使用 "的机制,使用
provide
和inject
配合实现。
-
provide(key, value)
:祖先组件中声明可供后代使用的内容 -
inject(key)
:后代组件中声明需要注入的内容
2、最基础的使用
父组件(祖先)使用 provide
<script setup>
import { provide } from 'vue'
provide('color', 'blue')
</script>
子组件(任意后代)使用 inject
<script setup>
import { inject } from 'vue'
const color = inject('color')
</script>
<template>
<p style="color: {{ color }}">我是注入的颜色</p>
</template>
✅ 这样,子组件就无需 props,可以跨越多层组件拿到祖先组件提供的数据。
如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。
3、为什么使用依赖注入?
传统方式 | 问题 |
---|---|
使用 props | 需要每层组件手动传递,维护困难 |
使用 Vuex / pinia | 重,但适合全局状态 |
使用 event bus | 不推荐,易维护混乱 |
✅ 依赖注入适合:
-
状态共享(如配置、主题、权限)
-
插件式封装(如表单、组件注册)
-
面向组合逻辑的解耦
默认值与类型检查
如果子组件注入的 key 不存在,返回值为 undefined
,你可以设置默认值:
const theme = inject('theme', 'light') // 默认值 fallback
你还可以传入工厂函数作为默认值:
const config = inject('config', () => ({ color: 'blue' }))
4、 使用 Symbol 作注入名
但如果你正在构建大型的应用,包含非常多的依赖提供,或者你正在编写提供给其他开发者使用的组件库,建议最好使用 Symbol 来作为注入名以避免潜在的冲突。
// keys.js
export const myInjectionKey = Symbol()
// 在供给方组件中
import { provide } from 'vue'
import { myInjectionKey } from './keys.js'
provide(myInjectionKey, {
/* 要提供的数据 */
})
// 注入方组件
import { inject } from 'vue'
import { myInjectionKey } from './keys.js'
const injected = inject(myInjectionKey)
二、异步组件
Vue 的 异步组件(Async Components) 是一种优化性能与用户体验的机制,它允许你在需要时按需加载某个组件,而不是在应用启动时一次性加载全部组件。
异步组件在构建大型应用、提升首屏加载速度、实现懒加载等场景中非常重要,Vue 为此提供了专门的语法和加载配置。
1、什么是异步组件?
异步组件是指:组件不是立即加载的,而是在需要时才异步地被加载。
它的核心思想是:"用的时候才加载",这对以下场景非常有用:
-
页面拆分(如路由页面)
-
大型组件库懒加载
-
多个组件中只有部分在运行中用到
2、最基础用法:defineAsyncComponent
Vue 提供了官方函数 defineAsyncComponent()
来创建异步组件。
基本语法:
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./MyHeavyComponent.vue')
)
这个组件会被延迟加载 ,只有在模板中首次渲染它时,Vue 才会触发 import()
加载它对应的文件。
3、在模板中使用异步组件
<template>
<div>
<AsyncComponent />
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./MyHeavyComponent.vue')
)
</script>
4、配置加载状态:加载提示 / 超时 / 错误处理
defineAsyncComponent
还可以接受一个对象,配置异步加载的过程行为。
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./MyHeavyComponent.vue'),
// 加载时展示的组件
loadingComponent: LoadingSpinner,
// 出错时展示的组件
errorComponent: ErrorMessage,
// 延迟多长时间显示 loadingComponent(毫秒)
delay: 200,
// 加载超时时间(毫秒)
timeout: 3000,
// 出错时重试机制
onError(error, retry, fail, attempts) {
if (attempts <= 3) {
retry()
} else {
fail()
}
}
})
各配置解释:
选项 | 功能 |
---|---|
loader |
必填。返回组件的异步加载 Promise(即 import() ) |
loadingComponent |
加载过程中显示的 UI |
errorComponent |
加载失败后显示的 UI |
delay |
加载多少毫秒后才显示 loading |
timeout |
超过此时间未加载完成视为失败 |
onError |
异步加载失败时自定义处理(可自动重试) |
注意:Vue Router 内部会自动处理 loading 状态,不需要你手动 defineAsyncComponent
。
三、生命周期
Vue 的生命周期(Lifecycle)指的是 Vue 组件从创建到销毁过程中经历的一系列阶段性钩子函数,你可以在这些钩子中执行逻辑,如数据初始化、DOM 操作、网络请求、清理等。
每个 Vue 组件实例在其创建过程中都会经历一系列初始化步骤,例如设置数据监听、编译模板、挂载 DOM,直到被销毁。Vue 提供了生命周期钩子函数,让我们有机会在不同阶段插入代码。
1、生命周期的本质:组件从"出生"到"死亡"的过程
整个过程大致如下:
创建阶段 → 挂载阶段 → 更新阶段 → 销毁阶段
2、生命周期钩子总览
阶段 | 钩子函数 | 作用 |
---|---|---|
创建前 | setup() |
Vue 3 中唯一入口 |
挂载前 | onBeforeMount() |
组件尚未挂载,DOM 还未生成 |
挂载后 | onMounted() |
组件已挂载,DOM 可操作 |
更新前 | onBeforeUpdate() |
响应式数据变更,但 DOM 还未更新 |
更新后 | onUpdated() |
DOM 更新完毕,可做响应处理 |
卸载前 | onBeforeUnmount() |
组件将被销毁,做清理前工作 |
卸载后 | onUnmounted() |
已销毁,清理资源、解绑事件等 |
3、 各生命周期钩子的详细解释
1. setup()
阶段 :组件创建阶段(最先执行)
调用时机 :组件实例刚创建,data
、props
、methods
都还没绑定到 this
上
作用:
-
是组合式 API 的入口;
-
用来声明响应式数据、props、emit、watch、计算属性等;
-
不能访问
this
; -
返回的变量会暴露给模板使用。
2. onBeforeMount()
阶段 :挂载前
调用时机 :组件即将挂载到 DOM(el
创建完毕但未插入 DOM)
作用:
-
此时模板已编译,但 DOM 未挂载;
-
可以进行某些准备工作或日志记录。
常见用途:
-
初始化数据日志;
-
注册未依赖 DOM 的第三方逻辑。
从组件"出生到上场"来理解生命周期
你可以把组件想象成一个"舞台演员":
阶段 | 比喻 | 生命周期钩子 |
---|---|---|
写好剧本 | 编译模板等准备工作 | setup() |
穿好衣服化妆 | DOM 虚拟结构准备就绪 | onBeforeMount() ✅ |
走上舞台 | 挂载 DOM,出现在页面 | onMounted() |
所以 onBeforeMount()
就是演员 刚准备好,马上就要上场 的那一刻。
Vue 在内部的执行流程:
setup() → 编译模板 → 创建虚拟 DOM →
→ 🔹onBeforeMount() →
→ 渲染并插入真实 DOM →
→ 🔹onMounted()
onBeforeMount 做了什么?
-
组件已经准备好数据(比如
props
、ref
); -
已经生成了虚拟 DOM(VNode);
-
但还没有插入真实页面中;
-
所以此时你不能直接操作 DOM 元素。
能干什么?
虽然还不能访问 DOM,但可以:
-
准备数据(如果依赖 DOM 可延迟);
-
进行日志埋点或调试;
-
记录组件加载时间;
-
检查组件状态是否完整;
-
准备动画过渡参数(比如进入动画前的状态)。
3. onMounted()
阶段 :挂载后
调用时机 :首次 DOM 渲染完成 ,所有 DOM 元素可操作
作用:
-
执行需要访问 DOM 的逻辑;
-
发起初始请求;
-
注册事件监听器;
-
引入第三方库(如 ECharts、Swiper)。
常见用途:
-
页面初始化请求;
-
DOM 操作(如获取滚动容器、焦点);
-
动画执行。
你可以把它理解为:
"我的组件已经出现在页面上了,现在可以操作它的 DOM、做异步请求、初始化动画、注册事件等。"
可以用 onMounted()
做哪些事?
用途 | 说明 |
---|---|
✅ 操作 DOM | 可以安全地访问 document.querySelector 或 ref 引用的元素 |
✅ 发起 API 请求 | 初始化组件数据,如加载表格数据、详情数据等 |
✅ 注册监听器 | 注册 scroll 、resize 、keyboard 等全局事件 |
✅ 使用第三方库 | 比如挂载图表(ECharts)、富文本编辑器(Quill)等 |
✅ 初始化动画 | 初始化进入动画或过渡效果 |
✅ 上报埋点 | 发送统计日志、埋点监控等 |
4. onBeforeUpdate()
阶段 :更新前
调用时机 :响应式状态变化,DOM 还未更新
作用:
-
可用于比较新旧状态;
-
做一些缓存或临时逻辑。
注意事项:
-
不建议操作 DOM;
-
不要进行异步逻辑处理。
onBeforeUpdate()
是 Vue 组件中的一个更新阶段生命周期钩子 ,它在 响应式数据发生变化、DOM 还没有更新之前 被调用。
你可以把它理解为:
"组件数据变了,DOM 马上要重新渲染了,现在是我插手干预的最后机会。"
onBeforeUpdate() 的作用
用途 | 说明 |
---|---|
✅ 获取旧的 DOM 状态 | DOM 还没变,你可以访问"变化前"的样子 |
✅ 计算更新前的一些值 | 比如滚动高度、旧位置 |
✅ 清除旧状态标记 | 如之前的样式、数据缓存 |
❌ 不适合做异步请求 | 更新前阶段不能引起新响应式更新(否则会递归) |
5.onUpdated()
阶段 :更新后
调用时机 :响应式数据更新完成,DOM 也已更新
作用:
-
可以对新 DOM 做处理;
-
常用于监听局部 DOM 变化后执行逻辑。
onUpdated()
是 Vue 组件在 响应式数据变更导致 DOM 更新完成后 自动调用的生命周期钩子。
你可以理解为:
"Vue 已经把新数据同步到了页面上,现在你可以看到更新后的 DOM,想操作它、获取它的新状态、触发动画等等------就靠
onUpdated()
。"
onUpdated() 能做什么?
能做的事 | 应用场景 |
---|---|
✅ 读取更新后的 DOM 状态 | 比如新的高度、内容、布局 |
✅ 执行过渡动画、样式变化 | 比如 fade-in、滚动动画等 |
✅ 动态调整布局 | 比如等高布局、表格自适应列宽 |
✅ 与旧值对比 | 和 onBeforeUpdate() 配合使用 |
❌ 不推荐用来发请求 | 页面每次更新都会触发,可能频繁调用 |
和onBeforeUpdate区别
项目 | onBeforeUpdate() |
onUpdated() |
---|---|---|
调用时机 | DOM 更新前 | DOM 更新后 |
访问的是 | 旧 DOM | 新 DOM |
场景 | 缓存、准备、清理 | 更新后调整布局、动画 |
能否操作新结构 | ❌ 不行 | ✅ 可以 |
理解类比:装修房子
你可以把 Vue 更新比喻成"家装翻新":
-
onBeforeUpdate()
:工人刚拿到设计图纸,准备改造,但还没开始动工; -
onUpdated()
:已经把墙刷好、灯装上、地板换了,可以检查结果了。
6. onBeforeUnmount()
阶段 :卸载前
调用时机 :组件即将从页面上被卸载
作用:
-
做清理工作,如清除定时器、取消监听器;
-
防止内存泄漏;
-
通知外部容器取消操作。
onBeforeUnmount()
是 Vue 组件即将被销毁前调用的生命周期钩子。
你可以理解为:
"Vue 准备把这个组件从页面中移除,我现在有一个机会做一些清理工作或资源释放。"
适合用 onBeforeUnmount()
做哪些事?
用途 | 举例 |
---|---|
✅ 清除定时器 | clearInterval() 、clearTimeout() |
✅ 取消网络请求 | 调用 AbortController.abort() 或取消 token |
✅ 移除事件监听器 | removeEventListener() |
✅ 停止动画或任务 | 比如取消动画帧 cancelAnimationFrame() |
✅ 通知父组件或全局状态 | 通知组件退出、清除缓存等 |
7. onUnmounted()
阶段 :卸载后
调用时机 :组件 DOM 被完全移除
作用:
-
彻底释放资源;
-
日志记录、事件通知等。
注意事项:
- 通常清理逻辑建议放在
onBeforeUnmount()
,onUnmounted()
比较少用。
onUnmounted()
是 Vue 组件在 被销毁之后 自动调用的生命周期钩子。
你可以理解为:
"Vue 已经把这个组件从页面中移除了,DOM 不再存在,响应式状态也都释放了。这是我最后一次处理事情的机会。"
onUnmounted()
的典型用途
用途 | 示例 |
---|---|
✅ 日志记录 / 调试 | 组件销毁后打 log |
✅ 通知外部状态 | 如 Vuex、Pinia 中的状态重置 |
✅ 执行异步销毁操作 | 提交状态到后端、更新埋点日志等 |
✅ 销毁订阅 / 解耦逻辑清理 | 比如 event bus 或 WebSocket 等注册事件 |
和 onBeforeUnmount()
的区别
钩子 | 时机 | 是否可以访问 DOM? | 用途 |
---|---|---|---|
onBeforeUnmount() |
销毁前 | ✅ 是 | 清除定时器/监听器 |
onUnmounted() |
销毁后 | ❌ 否 | 日志、通知、埋点、最后状态更新 |
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {}
}
4、钩子参数
指令的钩子会传递以下几种参数:
-
el
:指令绑定到的元素。这可以用于直接操作 DOM。 -
binding
:一个对象,包含以下属性。value
:传递给指令的值。例如在v-my-directive="1 + 1"
中,值是2
。oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是"foo"
。modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。instance
:使用该指令的组件实例。dir
:指令的定义对象。
-
vnode
:代表绑定元素的底层 VNode。 -
prevVnode
:代表之前的渲染中指令所绑定元素的 VNode。仅在beforeUpdate
和updated
钩子中可用。
四、组合式函数
Vue 3 中的组合式函数(Composables)是基于组合式 API 构建的一个逻辑复用机制,它允许你将组件的状态逻辑抽离成可重用的函数,这种方式比 Vue 2 的 Mixin、HOC 更清晰、更灵活、更易维护。
1、什么是组合式函数?
组合式函数 是一个以
useXxx()
命名的普通 JavaScript 函数,在其中使用 Vue 的组合式 API(如ref
,reactive
,watch
,computed
等),然后将这些逻辑封装并返回供多个组件使用。
简化理解就是:
"把多个组件中重复的响应式逻辑、状态、侦听器等代码封装成函数,复用它们。"
2、组合式函数的结构
一个组合式函数的基本结构如下:
// useCounter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
}
在组件中使用它:
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter()
</script>
<template>
<button @click="increment">点击:{{ count }}</button>
</template>
3、组合式函数的核心特征
特征 | 描述 |
---|---|
✅ 可重用 | 不同组件可调用相同的逻辑函数 |
✅ 组合式 API 支持 | 内部可以使用任何 ref、reactive、watch、computed 等 |
✅ 与组件解耦 | 逻辑不再依赖组件上下文 |
✅ 类型推导友好 | TypeScript 支持极好 |
✅ 易测试 | 普通函数,易于单元测试 |
4、组合式函数能做什么?
1. 封装状态逻辑
export function useToggle(initial = false) {
const state = ref(initial)
const toggle = () => (state.value = !state.value)
return { state, toggle }
}
2. 封装异步请求逻辑
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
fetch(url)
.then(res => res.json())
.then(json => (data.value = json))
.catch(err => (error.value = err))
return { data, error }
}
3. 封装监听器逻辑
export function useMouse() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onBeforeUnmount(() => window.removeEventListener('mousemove', update))
return { x, y }
}
5、实战场景汇总
场景 | 封装为组合式函数 |
---|---|
表单验证 | useFormValidation() |
网络请求 | useFetch() , useAxios() |
用户状态管理 | useUser() |
页面权限控制 | usePermission() |
响应式缓存 | useLocalStorage() , useSessionStorage() |
主题/系统设置 | useTheme() , useSystemConfig() |
响应式计数器 | useCounter() |
6、总结一句话
组合式函数是 Vue 3 中最推荐的逻辑复用机制 ,它允许你把多个组件重复的状态、侦听器、生命周期逻辑提取为独立函数,提高代码复用性、可维护性和清晰度。
五、插件
1、什么是 Vue 插件?
Vue 插件(Plugin)是一个可以通过
app.use()
方法注册到 Vue 应用中的扩展模块。
它通常用于:
-
向应用注入功能(如添加全局方法、指令、组件等)
-
对整个应用进行配置或初始化
-
集成第三方库(如 Vue Router、Vuex、Pinia、Element Plus 等)
2、插件的核心格式
Vue 插件其实就是一个具有 install(app, options)
方法的对象或函数:
插件的标准写法(对象形式):
// MyPlugin.js
export default {
install(app, options) {
// 1. 添加全局方法
app.config.globalProperties.$hello = () => {
console.log('Hello from plugin!')
}
// 2. 注册全局组件
app.component('MyGlobalComp', {
template: `<div>I am global</div>`
})
// 3. 添加全局指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 4. 注入 provide/inject 数据
app.provide('pluginName', 'MyPlugin')
}
}
3、注册方式
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import MyPlugin from './MyPlugin'
const app = createApp(App)
app.use(MyPlugin, { customOption: true }) // 安装插件
app.mount('#app')
4、插件内部能做的事(功能总结)
功能 | 用法 |
---|---|
注册全局组件 | app.component('Xxx', Component) |
注册全局指令 | app.directive('xxx', definition) |
添加全局属性 | app.config.globalProperties.$xxx = fn |
使用 provide/inject 传递全局依赖 |
app.provide('xxx', value) |
启动外部库或执行初始化 | 如初始化配置项、加载配置文件等 |
5、注意事项
注意点 | 说明 |
---|---|
插件必须使用 app.use() 安装 |
否则不会执行其 install() 方法 |
不要滥用全局属性 | app.config.globalProperties 会污染组件上下文 |
插件之间避免命名冲突 | 推荐用命名空间前缀(如 $myXxx ) |
插件应保持纯函数和无副作用 | 避免影响应用的稳定性 |
6、总结一句话
Vue 插件是通过
app.use()
安装的功能扩展模块,适合封装和共享全局逻辑,如注册组件、添加方法、集成第三方库等,是构建大型项目或封装组件库的重要机制。