前端面经-VUE3篇(二)--vue3组件知识(二)依赖注入、异步组件、生命周期、组合式函数、插件
目录
一、依赖注入
1、 依赖注入是什么?
2、最基础的使用
3、为什么使用依赖注入?
4、 使用 Symbol 作注入名
二、异步组件
1、什么是异步组件?
2、最基础用法:defineAsyncComponent
3、在模板中使用异步组件
4、配置加载状态:加载提示 / 超时 / 错误处理
三、生命周期
1、生命周期的本质:组件从“出生”到“死亡”的过程
2、生命周期钩子总览
3、 各生命周期钩子的详细解释
1. setup()
2. onBeforeMount()
3. onMounted()
4. onBeforeUpdate()
5.onUpdated()
6. onBeforeUnmount()
7. onUnmounted()
4、钩子参数
四、组合式函数
1、什么是组合式函数?
2、组合式函数的结构
3、组合式函数的核心特征
4、组合式函数能做什么?
5、实战场景汇总
6、总结一句话
五、插件
1、什么是 Vue 插件?
2、插件的核心格式
3、注册方式
4、插件内部能做的事(功能总结)
5、注意事项
6、总结一句话
一、依赖注入
通常情况下,当我们需要从父组件向子组件传递数据时,会使用 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.pageXy.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()
安装的功能扩展模块,适合封装和共享全局逻辑,如注册组件、添加方法、集成第三方库等,是构建大型项目或封装组件库的重要机制。