vue-usage
Vue 使用
Vue 是国内最普及的前端框架,面试考察概率最高�?
::: tip
如有疑问,可免费 加群 讨论咨询,也可参�?1v1 面试咨询服务�?专业、系统、高效、全流程 准备前端面试
:::
🔥Vue3 �?Vue2 的区别有哪些?说几个主要�?
Vue 3 提供了更现代化、更高性能的架构,通过 Composition API �?Proxy 响应式系统等改进提升了开发体�?:tada::tada::tada:。相比于 Vue 2,它的优势如�?
::: details 参考答�?
性能优化
虚拟 DOM 重构:Vue3的虚拟DOM采用了更高效�?
Diff算法,减少了渲染和更新的开销�?Tree-shaking 支持:Vue3的代码结构模块化,支持按需引入,减小了打包体积�?
Composition APIVue3引入了Composition API,使代码更模块化、复用性更强�?
使用
setup()方法代替了部分选项�?API,通过函数的方式组织逻辑,代码更加清晰简洁�?
*响应式系统改�?Vue3使用
Proxy实现响应式,解决�?Vue2使用Object.defineProperty实现响应式的一些局限性,如无法监听新增属性和数组索引变化�?
新特性和改进Teleport:可以将组件的DOM渲染到指定的DOM节点之外,例如模态框、通知等�?
Fragment 支持:Vue3支持组件返回多个根节点,不再需要单一根节点�?
Vue3原生支持
TypeScript,提供更完善的类型推导和开发体验�?Vue3支持为一个组件绑定多�?
v-model,并且可以自定义prop�?event名称�?
:::
🔥Vue 组件的通信方式有哪些?
组件之间的通信通常分为父子组件通信和跨组件通信。要注意,vue3 组件的通信方式�?vue2 有一定的区别�?
::: details 参考答�?
父子组件通信
Props:父组件通过
props向子组件传递数据�?- $emit:子组件通过$emit向父组件发送事件,并可以传递数据�?- 获取组件实例对象,调用属性或方法�? 💡 Vue 2:通过this.$parent获取父组件实例,或通过this.$children获取子组件实例�? 💡 Vue 3:通过ref引用子组件,直接访问其属性和方法�?- Vue 3 组件支持多个v-model绑定和自定义属性名,父子之间的双向绑定更加灵活�?
跨组件通信Provide / Inject:父组件通过
provide向后代组件传递数据,后代组件使用inject接收数据,适用于深层嵌套组件间的通信�?- vuex:通过全局状态管理库 Vuex 共享状态,实现跨组件通信(vue2)�?- pinia:Pinia �?Vue 3 推荐的全局状态管理库,替代了 Vuex�?- 事件总线(Vue 2):Vue 2 中可以通过Event Bus实现组件间的通信,但�?Vue 3 中不推荐使用�?- 全局事件处理器:通过在根组件$root或全局对象上监听事件,进行跨组件通信(Vue 3 推荐使用外部库,�?mitt)�? :::
🔥Vue 组件的生命周�?
Vue 组件的生命周期是指组件从创建到销毁的整个过程,包括组件的初始化、渲染、更新和销毁等阶段。在Vue2和Vue3中,组件的生命周期有一些区别�?
::: details Vue2
- 创建阶段
1️⃣ beforeCreate:组件实例刚被创建,数据观测和事�?监听器设置之前。此时无法访�?data �?computed �?methods 等�?2️⃣ created:组件实例已创建,数据观测、事�?监听器设置完成,此时可以访问 data �?computed �?methods 等,通常用于数据初始化�?
- 挂载阶段
3️⃣ beforeMount:在挂载开始之前,模板已编译, el �?template 已经确定,但尚未渲染�?4️⃣ mounted:组件实例挂载到 DOM 上之后,此时可以访问和操�?DOM�?
- 更新阶段
5️⃣ beforeUpdate:数据发生变化,DOM 尚未更新。可以在这里做一些数据处理,避免不必要的渲染�?6️⃣ updated:数据变化,DOM 更新后调用。此时组件的 DOM 已经更新,可以访问和操作新的 DOM�?
- *销毁阶�?
7️⃣ beforeDestroy:组件实例销毁之前。可以在此阶段进行清理工作,例如移除事件监听器、定时器等�?8️⃣ destroyed:组件实例销毁之后。此时,所有的事件监听器和子组件已被销毁�?
:::
::: details Vue3
- 挂载阶段
1️⃣ onBeforeMount:等效于 Vue 2 中的 beforeMount ,在组件挂载之前调用�?2️⃣ onMounted:等效于 Vue 2 中的 mounted ,在组件挂载之后调用�?
- 更新阶段
3️⃣ onBeforeUpdate:等效于 Vue 2 中的 beforeUpdate ,在数据更新之前调用�?4️⃣ onUpdated:等效于 Vue 2 中的 updated ,在数据更新并渲染之后调用�?
- *销毁阶�?
5️⃣ onBeforeUnmount:等效于 Vue 2 中的 beforeDestroy ,在组件卸载前调用�?6️⃣ onUnmounted:等效于 Vue 2 中的 destroyed ,在组件卸载后调用�?
:::
::: tip setup与生命周�?
setup 作为 Vue3 �?Composition API 的一部分, 其内部函数的执行时机早于Mounted钩子�?
1 | <script setup> |
:::
Vue 组件在哪个生命周期发�?ajax 请求�?
�?Vue中,接口请求一般放�?created �?mounted 生命周期钩子中�?
::: details 参考答�?
created 钩子
- 优点�? �?更快获取数据:能尽早获取服务端数据,减少页面加载时间�? �?SSR 支持:支持服务器端渲染(SSR),�?SSR 环境中不会受到限制�?
- 缺点
�?UI 未渲染时发起请求:如果需要操�?DOM 或渲染数据,可能导致闪屏问题
mounted 钩子
- 优点�? �?DOM 可用:适合需要操�?DOM 或渲染数据后再发起请求的情况,避免闪屏�?
- 缺点
�?请求延迟:数据请求会稍微延迟,增加页面加载时间�? �?**SSR 不支�?*:mounted只在客户端执行,不适用�?SSR 环境�?
:::
Vue 父子组件生命周期调用顺序
::: details 参考答�?
1️⃣ 创建阶段
父组件:
beforeCreate➡️created子组件:
beforeCreate➡️created顺序�? 父组件的
beforeCreate�?created先执�?,子组件�?beforeCreate�?created后执行�? > 原因:父组件需要先完成自身的初始化(如 data、computed 等),才能解析模板中的子组件并触发子组件的初始化�?
2️⃣ 挂载阶段父组件:
beforeMount子组件:
beforeMount➡️mounted父组件:
mounted顺序�? �?
beforeMount�?�?beforeCreate�?�?created�?�?beforeMount�?�?mounted�?�?mounted原因:父组件在挂载前(beforeMount)需要先完成子组件的渲染和挂载,因为子组件是父组件模板的一部分。只有当所有子组件挂载完成后,父组件才会触发自身的 mounted�?
3️⃣ 更新阶段父组件:
beforeUpdate子组件:
beforeUpdate➡️updated父组件:
updated顺序�? �?
beforeUpdate�?�?beforeUpdate�?�?updated�?�?updated原因:父组件的数据变化会触发自身更新流程,但子组件的更新必须在父组件更新前完成(因为子组件可能依赖父组件的数据),最终父组件的视图更新完成�?
4️⃣ 销毁阶�?父组件:
beforeDestroy子组件:
beforeDestroy➡️destroyed父组件:
destroyed顺序�? �?
beforeDestroy�?�?beforeDestroy�?�?destroyed�?�?destroyed原因:父组件销毁前需要先销毁所有子组件,确保子组件的资源释放和事件解绑,避免内存泄漏�?
::: tip
注:vue3中,setup()替代�?beforeCreate�?created,但父子组件的生命周期顺序不变�?
:::
🔥v-show �?v-if 的区�?
::: details 参考答�?
- *渲染方式�?
💡v-if:条件为 true 时才会渲染元素,条件�?false 时销毁元素�? 💡v-show:始终渲染元素,只是通过 CSS 控制display 属性来显示或隐藏�?- *适用场景�?
💡v-if:适用于条件变化不频繁的场景�? 💡v-show:适用于条件变化频繁的场景�?
:::
为何v-if和v-for不能一起使用?
v-if �?v-for 不能直接一起使用的原因,主要是因为它们�?*解析优先�? �?逻辑处理 上存在冲突�?
::: details 参考答�?
由于v-for 的解析优先级高于 v-if,同时使�?v-if �?v-for,Vue 首先会循环创建所有dom元素,然后根据条件来判断是否渲染每个元素,这种方式可能导�?Vue 进行大量�?DOM 操作,性能较差。其次,v-for 会为每个循环项创建一个新的作用域,�?v-if 的条件如果依赖于这个作用域内的数据,可能导致判断逻辑异常�?
为避免上述问题,vue官方推荐我们�?v-if 放到 v-for 外层,或者将 v-if 放置�?v-for 内部的单个节点上�?
1 | <div v-if="show"> |
:::
computed �?watch 有什么区�?
::: details 参考答�?
computed用于计算基于响应式数据的值,并缓存结�?
1 | <template> |
watch用于监听数据变化并执行副作用操作
1 | <template> |
| 特�? | computed |
watch |
|---|---|---|
| *用�? | 用于计算基于响应式数据的值,并缓存结�? | 用于监听数据变化并执行副作用操作 |
| *返回�? | 返回计算结果 | 不返回值,执行副作�? |
| 缓存机制 | 只有在访问时才会计算,会缓存计算结果,仅当依赖变化时重新计算 | 数据变化时立即执行回调,不缓存,每次数据变化都会触发回调 |
| 适用场景 | 计算派生数据,避免不必要的重复计�? | 执行异步操作、处理副作用操作,如 API 请求 |
| 性能 | 性能较好,避免重复计�? | 每次数据变化时都会执行回调函�? |
:::
🔥watch �?watchEffect 的区�?
watch �?watchEffect 都是 Vue 3 中用于响应式数据变化时执行副作用�?API,它们的使用场景和工作机制存在区别:
::: details 参考答�?
- 依赖追踪方式
watch :需要显式声明依赖,监听指定的数据源;可以监听多个数据源或进行深度监听�?
1 | import { watch, reactive } from 'vue' |
watchEffect :会自动追踪 **作用域内所有的响应式依�?*,不需要显式声明依赖�?
1 | import { watchEffect, reactive } from 'vue' |
- 执行时机
watch :在监听的响应式数据变化后立即执行�?watchEffect :在 *组件挂载�? 执行一次副作用,并�?*依赖发生变化�? 再次执行�?
- 适用场景
watch :适用�?监听特定数据 变化并执行副作用的场景,�?API 请求、保存操作等。适合需�?*访问新值和旧�? 进行比较的场景�?watchEffect :不需要访问旧值,适用�?*自动追踪多个响应式依�? 的副作用,如渲染、自动保存等�?
:::
Vue官方API�?watchEffect
🔥Vue3 ref �?reactive 如何选择�?
ref �?reactive 都是 Vue 3 中用来创建响应式数据�?API,他们的区别及使用场景如下�?
::: details 参考答�?
- reactive的实现:
reactive通过Proxy对对象或数组的每个属性进行深度代理,实现响应式。这种设计使�?reactive能自动追踪所有嵌套属性的变化,但由于Proxy无法直接处理基本数据类型(如number�?string�?boolean),因此�?reactive不适用于基本数据类型�? - ref的实现:
为了实现基本数据类型的响应式,Vue 设计�?ref�?ref会将基本数据类型封装为一个包�?value属性的对象,通过getter�?setter实现响应式依赖追踪和更新。当访问或修�?ref.value时,Vue 内部会触发依赖更新。此外,对于复杂数据类型(如对象或数组)�?ref的内部实现会直接调用reactive,将复杂数据类型变为响应式�?
::: tip 如何选择
Vue官方建议使用 ref() 作为声明响应式状态的主要,因�?reactive 存在以下局限性:
💡有限的值类型:它只能用于对象类�?(对象、数组和�?Map、Set 这样的集合类�?。它不能持有�?string、number �?boolean 这样的原始类型�?💡不能替换整个对象:由�?Vue 的响应式跟踪是通过属性访问实现的,因此我们必须始终保持对响应式对象的相同引用。这意味着我们不能轻易地“替换”响应式对象,因为这样的话与第一个引用的响应性连接将丢失�?
1 | let state = reactive({ |
💡对解构操作不友好:当我们将响应式对象的原始类型属性解构为本地变量时,或者将该属性传递给函数时,我们将丢失响应性连�?
1 | const state = reactive({ |
:::
什么是动态组件?如何使用它?
::: details 参考答�?
动态组件是 Vue 提供的一种机制,允许我们根据条件动态切换渲染的组件,而不需要手动修改模板�?在Vue中,我们可以通过 <component> 标签�?:is 属性指定需要渲染的组件�?
1 | <template> |
<component> 标签�?:is 属性值可以是�?
- 被注册的组件�?- 导入的组件对�?- 一般的 HTML 元素
当使�?<component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。如果需要保留动态组件状态,使用 <KeepAlive> 组件即可�?
:::
什么是 slot ,有什么应用场景?
slot �?Vue 中的一种用�?组件内容分发 的机制。它允许父组件向子组件插入内容,从而使组件更加灵活和可复用�?
::: details 参考答�?
在Vue中,插槽的使用方式可以分为四种:默认插槽�?*具名插槽**�?*条件插槽**�?*作用域插�?*�?
- 默认插槽
默认插槽是最简单的插槽形式,它允许我们将组件的内容传递到组件内部的一个占位符中�?
子组�?MyComponent.vue
1 | <template> |
父组�?
1 | <template> |
输出结果�?
1 | <div> |
- 具名插槽
当子组件需要多个插槽时,可以为插槽命名,并由父组件指定内容放置到哪个插槽�?
子组�?MyComponent.vue
1 | <template> |
父组�?
1 | <template> |
输出结果�?
1 | <div> |
v-slot有对应的简�?#,因�?<template v-slot:header>可以简写为<template #header>。其意思就是“将这部分模板片段传入子组件�?header 插槽中”�?
- 条件插槽
我们可以根据插槽是否存在来渲染某些内�?
子组�?MyComponent.vue
1 | <template> |
- *作用域插�?
作用域插槽可以让子组件在渲染时将一部分数据提供给插槽,从而实现父组件的插槽访问到子组件的状态�?
子组�?MyComponent.vue
1 | <template> |
1 | <template> |
输出结果�?
1 | <ul> |
Vue官方API�?插槽 Slots
::: tip 应用场景
💡灵活的组件内容插入:
插槽允许我们将内容插入组件中,而无需修改子组件内部逻辑,极大提高了组件的灵活性�?
💡构建通用组件�?比如开发卡片、模态框、列表等组件,使用插槽可以轻松实现内容的自定义。模态框组件可通过插槽自定义标题、正文和按钮�?
💡减少重复代码�?通过插槽,将公共逻辑封装到子组件中,而在父组件中只需插入变化的内容�?
:::
🚀Vue 项目可做哪些性能优化�?
🔍�?Vue 项目中,我们可以利用 Vue 特有的功能和机制实现性能优化�?
::: details 参考答�?
1️⃣ *模板和指令优�?
- 合理的使�?
v-if�?v-show指令,避免不必要的渲染�?- 使用v-for时,尽量提供唯一�?key,避免重复渲染�?- 使用v-once指令,只渲染一次,避免不必要的计算�?- 使用v-memo指令,对使用v-for生成的列表进行渲染优化。(vue3.2新增)
2️⃣ 组件优化
合理使用
keep-alive组件,缓存组件实例,避免重复渲染�?- 使用异步组件加载,减少首屏加载时间�?1
const AsyncComponent = defineAsyncComponent(() => import('./MyComponent.vue'))
配合 Vue Router 使用路由懒加载,实现路由页面按需加载�?- 合理划分组件,提升复用性和渲染性能�?
3️⃣ *响应式优�?使用
Object.freeze冻结对象,避免不必要的响应式�?- 使用 stop 停止 不必要的watchEffect副作用执行,以减少性能消耗�?- watch的优�?
💡 避免滥用深度监听,降低性能开销�?
💡 对于频繁触发的响应式数据变化,可以通过防抖和节流优化监听逻辑�?1
2
3
4
5
6
7
8import { debounce } from 'lodash'
watch(
() => searchQuery,
debounce((newQuery) => {
fetchSearchResults(newQuery)
}, 300)
)
💡 可以通过返回函数只监听具体的依赖,减少不必要的触发�?
1 | watch([() => user.name, () => user.age], ([newName, newAge]) => { |
💡 当监听器在某些条件下不再需要时,可以通过返回�?stop 方法手动停止监听,以节省资源
1 | const stop = watch( |
💡 当多个监听器的回调逻辑类似时,可以合并监听
1 | watch([() => user.name, () => user.age], ([newName, newAge]) => { |
:::
什么是 nextTick 如何应用�?
::: details 参考答�?
�?Vue.js 中, nextTick 是一个核心工具方法,用于处理 DOM 更新时机问题。它的核心作用是�?在下�?DOM 更新循环结束后执行回调,确保我们能操作到最新的 DOM 状态�?
它的使用场景如下�?
- 数据变化后操�?DOM
1 | <script setup> |
- 在生命周期钩子中操作 DOM
1 | <script setup> |
注意,在vue2中和vue3的选项�?API中,我们使用this.$nextTick(callback)的方式调用�?
1 | this.$nextTick(() => { |
:::
使用 Vue3 Composable 组合式函数,实现 useCount
::: tip
�?Vue 应用的概念中,�?*组合式函�?*�?Composables) 是一个利�?Vue 的组合式 API 来封装和复用有状态逻辑的函数。它和自定义 React hooks 非常相似�?
:::
使用组合式函数实现如下需求:useCount 是一个计数逻辑管理的组合式函数,它返回一�?count 变量和增加、减少、重置count的方法�?
::: details 参考答�?
1 | <script setup> |
:::
使用 Vue3 Composable 组合式函数,实现 useRequest
1 | const { loading, data, error } = useRequest(url) // 可只考虑 get 请求 |
::: details 参考答�?
1 | import { ref, computed } from 'vue'; |
使用
1 | <script setup> |
:::
自定义组件如何实�?v-model
v-model 可以在组件上使用以实现双向绑定�?
::: details vue2
在vue2中,自定义组件使�?v-model ,需要在组件内部定义 value prop,然后通过 this.$emit('input', newValue) 触发更新即可�?
1 | <!-- CustomInput.vue --> |
使用方式�?
1 | <CustomInput v-model="searchText" /> |
:::
::: details vue3
与vue2类似,vue3自定义组件使�?v-model ,需要在组件内部定义 modelValue prop,然后通过 emit('update:modelValue', newValue) 触发更新
1 | <!-- CustomInput.vue --> |
使用方式�?
1 | <CustomInput v-model="searchText" /> |
👉注意,从 Vue 3.4 开始,官方推荐的实现方式是使用 defineModel() 宏:
1 | <!-- Child.vue --> |
父组件使�?v-model 绑定一个值:
1 | <!-- Parent.vue --> |
defineModel 是一个便利宏,其返回的值是一�?ref 。它可以像其�?ref 一样被访问以及修改,不过它能起到在父组件和当前变量之间的双向绑定的作用�?
- 它的
.value和父组件�?v-model的值同步; - 当它被子组件变更了,会触发父组件绑定的值一起更新�? 根据
defineModel的特性,我们可以�?v-model把这�?ref绑定到一个原�?input元素上:
1 | <script setup> |
此外,v-model 可以接受自定义参数、添加修饰符,组件也可以绑定多个 v-model ,具体用法请参�?> 官网文档:组件 v-model
:::
如何统一监听 Vue 组件报错
::: details 参考答�?
�?Vue 3 中,可以通过 全局错误处理�?(errorHandler) �?生命周期钩子(例�?onErrorCaptured )来统一监听和处理组件中的错误�?
- 通过全局错误处理�?
app.config.errorHandler
1 | import { createApp } from 'vue'; |
- *局部错误捕获(onErrorCaptured�?
onErrorCaptured 钩子可以捕获后代组件传递过程中的错误信�?
1 | <script setup> |
Vue官方API�?onErrorCaptured、errorHandler
:::
Vuex �?mutation �?action 有什么区别?
�?Vuex 中, mutation �?action 是用于管理状态的两种核心概念�?
::: details 参考答�?mutation 可以直接修改 store 中的 state值,它只支持同步操作�?Action 不能直接修改 state,而是通过调用 mutation 来间接修改,它用于处理异步操作�?
1 | const store = createStore({ |
1 | const store = createStore({ |
*总结�?
| 特�? | Mutation | Action |
|---|---|---|
| 是否同步 | �?同步 | �?异步(也可以处理同步�? |
| 是否直接修改 state | �?直接修改 | �?通过调用 mutation 修改 |
| 调用方式 | commit('mutationName') |
dispatch('actionName') |
| 适用场景 | 简单的状态修�? | 异步操作(如 API 调用�? |
| 调试支持 | 完全支持,易于追�? | 依赖�?mutation 的日�? |
::: tip ⚠️ 为什么要有这样的区分�?
- 数据可预测性:通过强制
Mutation同步修改 State,使得状态变更可追踪 - 调试友好性:DevTools 可以准确捕捉每次状态快�?- 代码组织:将同步逻辑与异步逻辑分离,提高代码可维护�?
:::
参考文章:VueX用法快速回顾
Vuex �?Pinia 有什么区别?
::: details 参考答�?Pinia �?Vuex 都是 Vue 的专属状态管理库,允许用户跨组件或页面共享状态�?
- 区别
| 特�? | Vuex | Pinia |
|---|---|---|
| 版本支持 | Vue 2 �?Vue 3 | 仅支�?Vue 3(基�?Composition API �? |
| API 风格 | 基于传统的对象式 API | 基于 Composition API,类似于 setup 语法 |
| 模块管理 | 支持模块化(modules),但语法较复杂 | 模块化简单,*每个 store 就是一个独立模�? |
| TypeScript 支持 | TypeScript 支持不完善,需手动定义类型 |
开箱即用的 TypeScript 支持,类型推导更强大 |
| 性能 | 更适合大型项目,但冗余代码较多 | 更加轻量,性能更好,支持按需加载 |
| 状态持久化 | 需要额外插�? | 插件系统更加灵活,支持状态持久化插件 |
- 代码对比
📝 vuex
1 | // store.js |
vue组件中使�?
1 | <script> |
📝Pinia
1 | // store.js |
组件中使用:
1 | <script setup> |
- *如何选择�?
对于vue3项目,官方推荐使用pinia。因为它更轻量、TypeScript 支持更好、模块化更简单且拥有更强�?DevTools 支持�?
:::
Vue-router 导航守卫能用来做什么?
::: details 参考答�?vue Router �?*导航守卫用于在路由跳转过程中对导航行为进�?*拦截**�?*控制。这些守卫在路由进入、离开或更新时执行,可以用于多种场景,确保应用的导航逻辑符合预期。以下是常见的用途:
认证和授�?
用于检查用户的登录状态或权限,防止未授权用户访问受限页面�?1
2
3
4
5
6
7
8router.beforeEach((to, from, next) => {
const isAuthenticated = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login') // 未登录,跳转到登录页
} else {
next() // 已登录,正常导航
}
})数据预加�?
在进入路由前预加载必要的数据,确保页面渲染时数据已准备好�?1
2
3
4
5router.beforeEach(async (to, from, next) => {
if (to.name === 'userInfo') {
await store.dispatch('fetchUserData') // 预加载用户数�? }
next()
})动态修改页面标�?
根据路由信息动态更改浏览器标签页的标题,提升用户体验�?1
2
3router.afterEach((to) => {
document.title = to.meta.title || '自定义标�?
})动画和加载效�?
在路由切换时展示加载动画或过渡效果,提升用户体验�?1
2
3
4
5
6
7router.beforeEach((to, from, next) => {
store.commit('setLoading', true) // 开始加载动�? next()
})
router.afterEach(() => {
store.commit('setLoading', false) // 结束加载动画
})日志记录和分�?
在路由切换时记录用户行为,用于分析或调试�?1
2
3router.afterEach((to, from) => {
console.log(`用户�?${from.fullPath} 跳转�?${to.fullPath}`)
})防止访问不存在的页面
通过守卫检查路由是否存在,避免导航到无效页面�?
1 | router.beforeEach((to, from, next) => { |
关联文章:5min带你快速回顾、学习VueRouter的使用!
:::