1. 什么是 Vue.js?它的核心特点是什么?
Vue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。它的核心特点包括:
-
响应式数据绑定
-
组件化开发
-
虚拟 DOM
-
指令系统
-
轻量级且易于集成
-
丰富的生态系统(Vue Router, Vuex, Vue CLI等)
2. Vue 的双向数据绑定原理是什么?
Vue 使用数据劫持结合发布者-订阅者模式实现双向绑定:
1.通过 Object.defineProperty() 或 Proxy 对数据对象进行劫持
2. 在 getter 中收集依赖(Watcher)
3. 在 setter 中通知变化,触发更新
4. 编译器解析模板指令,初始化视图
5. Watcher 作为桥梁,连接 Observer 和 Compile
3. Vue 2.x 和 Vue 3.x 的主要区别有哪些?
1. 响应式系统:Vue 2 使用 Object.defineProperty,Vue 3 使用 Proxy
2. 性能:Vue 3 打包体积更小,虚拟 DOM 重写,性能更好
3. Composition API:Vue 3 引入新的代码组织方式
4. 片段支持:Vue 3 支持多根节点组件
5. Teleport 和 Suspense:Vue 3 新增的内置组件
6. TypeScript 支持:Vue 3 有更好的 TS 集成
7. 自定义渲染器 API
8. 全局 API 改为应用实例 API
4. 什么是 MVVM 模式?Vue 是如何实现 MVVM 的?
MVVM 是 Model-View-ViewModel 的缩写:
- Model:数据模型
- View:UI 界面
- ViewModel:连接 View 和 Model 的桥梁
Vue 实现 MVVM:
1. View:模板(template)
2. Model:数据对象(data)
3. ViewModel:Vue 实例,通过响应式系统和模板编译连接 View 和 Model
5. Vue 的生命周期钩子有哪些?分别在什么阶段调用?
Vue 2.x 生命周期:
beforeCreate:实例初始化后,数据观测之前
created:实例创建完成,数据观测完成
beforeMount:挂载开始之前
mounted:实例挂载到 DOM 后
beforeUpdate:数据更新时,DOM 更新前
updated:数据更新后,DOM 更新完成
beforeDestroy:实例销毁前
destroyed:实例销毁后
Vue 3.x 对应:
beforeCreate → 使用 setup()
created → 使用 setup()
beforeMount → onBeforeMount
mounted → onMounted
beforeUpdate → onBeforeUpdate
updated → onUpdated
beforeUnmount → onBeforeUnmount
unmounted → onUnmounted
6. Vue 组件间通信的方式有哪些?
1. 父子组件通信:
Props 向下传递
自定义事件向上传递 ($emit)
v-model / .sync 语法糖
parent/children (不推荐)
ref 获取组件实例
2. 兄弟组件通信:
通过共同的父组件中转
Event Bus
Vuex
3. 跨层级通信:
provide / inject
Vuex
全局事件总线
4. 其他:
attrs/listeners (Vue 2)
v-bind="$attrs" (Vue 3)
状态管理库 (Vuex, Pinia)
7. 什么是单向数据流?为什么 Vue 要采用这种设计?
单向数据流是指:
父组件通过 props 向子组件传递数据
子组件不能直接修改 props,只能通过事件通知父组件修改
原因:
使数据流更易于理解和调试
防止子组件意外修改父组件状态
降低组件间耦合度
更好的维护性和可预测性
8. 动态组件是什么?如何使用?
动态组件是通过 <component>
元素和 is 特性实现的组件动态切换:
vue
<component :is="currentComponent"></component>
使用场景:
标签页切换
根据条件渲染不同组件
动态布局系统
注意事项:
可以使用 <keep-alive> 缓存组件状态
切换时组件会销毁和重建
9. 异步组件是什么?如何实现?
异步组件是按需加载的组件,可以提高首屏加载速度。
Vue 2.x 实现:
javascript
components: {
AsyncComponent: () => import('./AsyncComponent.vue')
}
Vue 3.x 新增 defineAsyncComponent:
javascript
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
高级配置:
javascript
const AsyncComp = defineAsyncComponent({
loader: () => import('./Foo.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000
})
10. 什么是函数式组件?有什么特点?
函数式组件是无状态、无实例的组件,渲染开销小。
特点:
没有响应式数据
没有实例(this)
只接受 props
无生命周期钩子
渲染性能高
Vue 2.x 声明:
javascript
Vue.component('functional-comp', {
functional: true,
render(h, context) {
// ...
}
})
Vue 3.x 声明:
javascript
import { h } from 'vue'
const FunctionalComp = (props, context) => {
return h('div', props.msg)
}
11. v-if 和 v-show 的区别是什么?
v-if:条件渲染,元素不存在于 DOM 中,切换时销毁/重建组件
v-show:条件显示,元素始终存在于 DOM 中,只是切换 display 属性
使用场景:
v-if:切换频率低,条件不太可能改变
v-show:频繁切换,初始渲染成本高
性能考虑:
v-if 有更高的切换开销
v-show 有更高的初始渲染开销
12. v-for 指令的 key 属性有什么作用?
key 的作用:
帮助 Vue 识别节点身份,高效更新虚拟 DOM
避免就地复用元素
维护组件状态和子组件状态
最佳实践:
使用唯一标识作为 key
避免使用索引作为 key(当列表顺序变化时会有问题)
在 v-for 中总是提供 key
13. v-model 的原理是什么?如何自定义 v-model?
答案:
v-model 是语法糖,默认相当于:
vue
<input :value="value" @input="value = $event.target.value" />
自定义组件 v-model (Vue 2.x):
javascript
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
}
Vue 3.x 支持多个 v-model:
vue
<ChildComponent v-model:title="title" v-model:content="content" />
14. Vue 有哪些内置指令?各自的作用是什么?
答案:
常用内置指令:
v-text:更新元素的 textContent
v-html:更新元素的 innerHTML
v-show:条件显示
v-if/v-else-if/v-else:条件渲染
v-for:列表渲染
v-on (@):绑定事件
v-bind (:):绑定属性
v-model:双向绑定
v-slot (#):插槽
v-pre:跳过编译
v-once:只渲染一次
v-memo:记忆渲染 (Vue 3.2+)
v-cloak:防止闪现
15. 自定义指令是什么?如何实现?
答案:
自定义指令用于对普通 DOM 元素进行底层操作。
注册全局指令:
javascript
// Vue 2
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
// Vue 3
app.directive('focus', {
mounted(el) {
el.focus()
}
})
注册局部指令:
javascript
directives: {
focus: {
mounted(el) {
el.focus()
}
}
}
钩子函数 (Vue 2 → Vue 3):
bind → beforeMount
inserted → mounted
update → 移除,改用 updated
componentUpdated → updated
unbind → unmounted
16. Vue 2.x 的响应式原理是什么?有什么缺陷?
答案:
原理:
使用 Object.defineProperty 劫持对象属性
在 getter 中收集依赖
在 setter 中通知更新
每个组件实例对应一个 Watcher 实例
缺陷:
无法检测对象属性的添加或删除(需要 Vue.set/Vue.delete)
无法检测数组索引和长度的变化
对 ES6+ 的 Map、Set 等不支持
初始化时递归遍历所有属性,性能有影响
17. Vue 3.x 的响应式系统有什么改进?
答案:
改进:
使用 Proxy 代替 Object.defineProperty
可以检测属性添加/删除
支持 Map、Set 等数据结构
性能优化:
惰性响应式(按需响应)
更好的缓存机制
更精确的变更检测
支持嵌套对象的自动解包
独立的响应式模块(可单独使用)
18. Vue.set/Vue.delete 的作用是什么?Vue 3 中还需要吗?
答案:
Vue 2.x 中:
Vue.set:向响应式对象添加新属性并触发视图更新
Vue.delete:删除属性并触发视图更新
Vue 3.x 中:
不再需要,因为 Proxy 可以检测到属性的添加和删除
但仍保留了 set 和 delete 的 API 用于兼容
19. 什么是响应式数据的副作用函数?Vue 中如何收集依赖?
答案:
副作用函数是指会对外部产生影响的函数,如修改 DOM、发送请求等。
Vue 收集依赖的过程:
在组件渲染时,会执行 render 函数访问响应式数据
触发数据的 getter,将当前 Watcher(副作用函数)添加到依赖中
数据变化时,触发 setter,通知所有依赖的 Watcher 更新
Watcher 执行更新函数,重新渲染或执行副作用
20. computed 和 watch 的区别是什么?
答案:
computed:
计算属性,基于依赖缓存
必须有返回值
适合派生状态
同步操作
watch:
观察特定数据变化
无返回值
适合执行异步或复杂操作
可以观察多个数据源
使用场景:
computed:模板中的复杂表达式、数据格式化
watch:数据变化时需要执行异步操作或复杂逻辑
虚拟 DOM 和渲染
21. 什么是虚拟 DOM?它的优点是什么?
答案:
虚拟 DOM 是真实 DOM 的轻量级 JavaScript 表示。
优点:
减少直接操作 DOM 的性能开销
提供跨平台能力(如 SSR、Native 渲染)
高效的差异比较算法(diff)
批量更新 DOM
开发体验更接近声明式编程
工作原理:
用 JavaScript 对象表示 DOM 结构
状态变化时生成新的虚拟 DOM
比较新旧虚拟 DOM (diff)
将差异应用到真实 DOM (patch)
22. Vue 的 diff 算法是怎样的?
答案:
Vue 的 diff 算法特点:
同级比较,不跨级
使用 key 识别节点身份
双端比较策略(Vue 3 优化为更高效的算法)
优先处理特殊情况(如相同节点、文本节点)
优化策略:
静态节点提升(Vue 3)
事件缓存
区块树优化(Vue 3)
更高效的 patch 标志
23. Vue 3 在虚拟 DOM 方面有哪些优化?
答案:
Vue 3 虚拟 DOM 优化:
静态提升:将静态节点提升到渲染函数外
补丁标志:为动态节点添加标志,减少比较范围
缓存事件处理函数
区块树:将静态和动态内容分离
更高效的 diff 算法
支持片段(多根节点)
更快的挂载和更新性能
24. 什么是渲染函数?什么情况下需要使用渲染函数?
答案:
渲染函数是用于编程式创建虚拟 DOM 的函数(h 函数)。
使用场景:
动态性很强的组件
需要更灵活的模板逻辑
高阶组件
需要 JavaScript 的完整编程能力
性能敏感场景(比模板更高效)
示例:
javascript
render(h) {
return h('div', { class: 'container' }, [
h('h1', 'Title'),
this.showSubtitle ? h('h2', 'Subtitle') : null
])
}
25. Vue 3 的 h 函数有什么变化?
答案:
Vue 3 中 h 函数的变化:
需要从 vue 显式导入
更灵活的参数:
可以省略不用的参数
props 结构更一致
支持 VNode 的标准化
更好的 TypeScript 支持
Vue 2:
vue
h('div', { class: 'foo', on: { click: handler } }, 'hello')
Vue 3:
vue
h('div', { class: 'foo', onClick: handler }, 'hello')
路由系统
26. Vue Router 的导航守卫有哪些?如何使用?
答案:
导航守卫分类:
全局守卫:
beforeEach:前置守卫
beforeResolve:解析守卫
afterEach:后置钩子
路由独享守卫:
beforeEnter
组件内守卫:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
使用示例:
vue
router.beforeEach((to, from, next) => {
// 必须调用 next()
})
// 组件内
beforeRouteLeave(to, from, next) {
// 确认离开?
next(confirm('确认离开?'))
}
27. 路由懒加载的原理是什么?如何实现?
答案:
路由懒加载是通过动态导入实现的代码分割技术。
原理:
使用动态 import() 语法
Webpack 将其识别为代码分割点
路由被访问时才加载对应 chunk
实现:
vue
const routes = [
{
path: '/about',
component: () => import('./views/About.vue')
}
]
Vue 3 还可以使用 defineAsyncComponent:
vue
const About = defineAsyncComponent(() => import('./About.vue'))
28. Vue Router 有几种路由模式?区别是什么?
答案:
两种主要模式:
hash 模式:
使用 URL hash (#)
兼容性好
不需要服务器配置
示例:http://example.com/#/about
history 模式:
使用 HTML5 History API
URL 更美观
需要服务器配置(避免 404)
示例:http://example.com/about
配置:
vue
const router = createRouter({
history: createWebHashHistory(), // hash
history: createWebHistory(), // history
})
29. 如何实现动态路由?有哪些应用场景?
答案:
实现方式:
使用冒号定义动态参数:
{ path: '/user/:id', component: User }
通过 props 接收参数:
{ path: '/user/:id', component: User, props: true }
编程式导航:
router.push('/user/' + userId)
应用场景:
用户个人主页
商品详情页
博客文章页
任何需要根据 ID 展示不同内容的页面
30. 如何实现路由鉴权?
答案:
常见鉴权方案:
路由元信息 + 全局守卫:
vue
{
path: '/admin',
meta: { requiresAuth: true }
}
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.isLoggedIn()) {
next('/login')
} else {
next()
}
} else {
next()
}
})
动态路由:
根据权限动态生成路由表
使用 router.addRoute() 添加路由
组合式 API (Vue 3):
vue
import { onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteUpdate((to, from, next) => {
// 鉴权逻辑
})
31. Vuex 的核心概念有哪些?
答案:
Vuex 核心概念:
State:单一状态树
Getters:计算属性
Mutations:同步修改状态(commit)
Actions:异步操作(dispatch)
Modules:模块化
工作流程:
组件 dispatch Action
Action commit Mutation
Mutation 修改 State
State 变化触发组件更新
32. Vuex 和 Pinia 的主要区别是什么?
答案:
Pinia 是 Vuex 的替代方案,主要区别:
API 设计:
Vuex 有 mutations/actions
Pinia 只有 actions(可同步/异步)
TypeScript 支持:
Pinia 有更好的 TS 支持
模块化:
Vuex 需要 modules
Pinia 自动模块化
体积:
Pinia 更轻量
Composition API:
Pinia 专为 Vue 3 设计
开发体验:
Pinia 更简洁直观
33. 什么情况下应该使用 Vuex/Pinia?
答案:
使用场景:
多个组件共享状态
多个组件需要修改同一状态
需要维护复杂的状态逻辑
需要状态的时间旅行调试
需要服务端渲染的状态保持
不适合场景:
简单应用(可以用 provide/inject)
父子组件通信(用 props/emit)
简单全局状态(可以用 reactive)
34. 如何实现 Vuex 的模块热重载?
答案:
Vuex 模块热重载配置:
vue
if (module.hot) {
module.hot.accept(['./modules/moduleA'], () => {
const newModuleA = require('./modules/moduleA').default
store.hotUpdate({
modules: {
moduleA: newModuleA
}
})
})
}
Pinia 自动支持热更新:
javascript
if (import.meta.hot) {
import.meta.hot.accept(acceptHMRUpdate(useStore, import.meta.hot))
}
35. Vuex 的严格模式是什么?有什么作用?
答案:
严格模式会深度监测状态变更是否来自 mutation。
启用:
vue
const store = new Vuex.Store({
strict: true
})
作用:
确保所有状态变更都经过 mutation
防止在 mutation 外修改状态
开发时更好的调试体验
注意:
不要在生产环境使用(性能影响)
可以用发布时去除:
strict: process.env.NODE_ENV !== 'production'
组合式 API
36. 什么是组合式 API?解决了什么问题?
答案:
组合式 API 是 Vue 3 引入的新代码组织方式,包括:
setup 函数
ref 和 reactive
生命周期钩子函数
自定义组合函数
解决的问题:
选项式 API 在复杂组件中逻辑分散
更好的逻辑复用
更好的 TypeScript 支持
更灵活的逻辑组织
更小的函数粒度
37. setup 函数是什么?如何使用?
答案:
setup 是组合式 API 的入口函数:
在 beforeCreate 之前调用
没有 this
接收 props 和 context 参数
返回对象暴露给模板
示例:
vue
setup(props, context) {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
}
context 包含:
attrs
slots
emit
expose
38. ref 和 reactive 的区别是什么?
答案:
ref:
用于基本类型(也可以用于对象)
通过 .value 访问
模板中自动解包
更适合独立的基本值
reactive:
用于对象
直接访问属性
解构会失去响应性
更适合复杂对象
转换:
reactive({ count: 1 }) 类似 ref(1).value
ref(obj) 类似 reactive(obj)
39. 什么是自定义 hook?如何实现?
答案:
自定义 hook 是使用组合式 API 封装的逻辑复用函数。
实现:
vue
// useCounter.js
import { ref } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
function increment() {
count.value++
}
return {
count,
increment
}
}
// 使用
vue
import { useCounter } from './useCounter'
setup() {
const { count, increment } = useCounter()
return { count, increment }
}
特点:
以 use 前缀命名
可以组合其他 hook
返回响应式状态和方法
40. watch 和 watchEffect 的区别是什么?
答案:
watch:
明确指定侦听源
惰性执行(默认)
可以获取旧值
更精确控制触发时机
watchEffect:
自动收集依赖
立即执行
没有旧值
适合副作用清理
示例:
javascript
// watch
watch(count, (newVal, oldVal) => {})
// watchEffect
watchEffect(() => {
console.log(count.value)
})
清理副作用:
javascript
watchEffect((onInvalidate) => {
const timer = setInterval(() => {})
onInvalidate(() => clearInterval(timer))
})
41. 什么是 Teleport?使用场景是什么?
答案:
Teleport 是 Vue 3 内置组件,可以将内容渲染到 DOM 树的指定位置。
使用:
javascript
<teleport to="body">
<div class="modal">...</div>
</teleport>
场景:
模态框
通知提示
加载条
任何需要突破父组件 DOM 层级限制的 UI
注意:
to 可以是 CSS 选择器或 DOM 元素
内容仍保持组件上下文(如数据、指令)
42. 什么是 Suspense?如何使用?
答案:
Suspense 是 Vue 3 内置组件,用于处理异步组件加载状态。
使用:
javascript
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
工作原理:
等待异步组件加载
显示 fallback 内容
加载完成后显示默认内容
注意:
实验性功能,API 可能变化
可以配合 async setup 使用
43. 什么是渲染函数 JSX?如何在 Vue 中使用 JSX?
答案:
JSX 是 JavaScript 的语法扩展,可以在 JavaScript 中编写类似 HTML 的结构。
Vue 中使用:
安装插件:
npm install @vue/babel-plugin-jsx -D
配置 babel:
plugins: ["@vue/babel-plugin-jsx"]
使用:
javascript
render() {
return <div class="container">{this.message}</div>
}
Vue 3 中:
javascript
import { defineComponent } from 'vue'
const Component = defineComponent({
setup() {
return () => <div>JSX in Vue 3</div>
}
})
44. Vue 3 的 Fragment 是什么?有什么好处?
答案:
Fragment 允许组件有多个根节点。
Vue 2 中:
组件必须有且只有一个根元素
Vue 3 中:
... ... ...
好处:
更灵活的模板结构
减少不必要的包装 div
更符合语义化
与 React 保持一致
45. 什么是自定义渲染器?使用场景是什么?
答案:
自定义渲染器 API 允许自定义 Vue 的渲染逻辑。
使用场景:
非 DOM 渲染:
小程序
Canvas
WebGL
终端输出
特殊 DOM 操作需求
创建特定领域的框架
示例:
javascript
import { createRenderer } from 'vue'
const { render, createApp } = createRenderer({
patchProp,
insert,
remove,
createElement
// ...其他平台特定API
})
46. Vue 应用常见的性能优化手段有哪些?
答案:
常见优化:
代码层面:
合理使用 v-if 和 v-show
为 v-for 设置 key
避免同时使用 v-if 和 v-for
使用 computed 缓存计算
拆分复杂组件
使用 keep-alive 缓存组件
打包层面:
路由懒加载
按需引入组件库
代码分割
压缩代码
Tree-shaking
运行时:
减少响应式数据量
避免不必要的组件渲染
防抖/节流
虚拟滚动 (virtual scroller)
其他:
SSR
CDN
服务端缓存
47. 什么是 keep-alive?如何使用?
答案:
keep-alive 是 Vue 内置组件,用于缓存不活动的组件实例。
使用:
javascript
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
属性:
include:匹配的组件名会被缓存
exclude:匹配的组件名不会被缓存
max:最多缓存组件实例数
生命周期:
activated:组件激活时
deactivated:组件停用时
48. Vue 3 的 v-memo 是什么?如何使用?
答案:
v-memo 是 Vue 3.2+ 新增指令,用于缓存模板子树。
使用:
javascript
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
{{ item.text }}
</div>
工作原理:
依赖项不变时跳过更新
必须与 v-for 一起使用
可以显著提升性能
场景:
大型列表
复杂条件渲染
频繁更新的 UI
49. 如何分析 Vue 应用的性能瓶颈?
答案:
分析方法:
Vue Devtools:
性能时间线
组件渲染时间
事件追踪
Chrome DevTools:
Performance 面板
Memory 面板
Coverage 面板
特定工具:
webpack-bundle-analyzer
speed-measure-webpack-plugin
Lighthouse
代码层面:
检查不必要的重新渲染
检查大型响应式对象
检查内存泄漏
50. 如何实现虚拟滚动?有什么好处?
答案:
虚拟滚动只渲染可见区域的列表项。
实现方式:
使用第三方库:
vue-virtual-scroller
vue-virtual-scroll-grid
手动实现:
计算可见区域
动态渲染可见项
处理滚动事件
好处:
减少 DOM 数量
提高渲染性能
支持超大型列表
更流畅的滚动体验
51. Vue 组件的单元测试应该测试哪些方面?
答案:
测试重点:
渲染输出:
是否正确渲染
条件渲染逻辑
列表渲染
用户交互:
点击等事件
表单输入
自定义事件
状态变更:
props 变化
数据变化
计算属性
生命周期:
挂载/卸载
更新
异步行为:
API 调用
定时器
52. 常用的 Vue 测试工具和库有哪些?
答案:
常用工具:
测试运行器:
Jest
Vitest
测试工具:
Vue Test Utils (官方)
Testing Library
辅助工具:
@vue/test-utils
vue-jest
jest-transform-stub
其他:
Cypress (E2E)
Storybook (可视化测试)
53. 如何测试 Vue 组件中的异步逻辑?
答案:
测试异步逻辑方法:
使用 async/await:
javascript
test('async test', async () => {
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('Updated')
})
模拟定时器:
javascript
jest.useFakeTimers()
// 触发定时器
jest.runAllTimers()
模拟 API 调用:
javascript
jest.mock('axios')
axios.get.mockResolvedValue({ data: {} })
flush-promises:
javascript
await flushPromises()
54. 什么是快照测试?在 Vue 中如何实现?
答案:
快照测试是比较组件渲染结果与保存的快照是否一致。
实现:
javascript
test('component snapshot', () => {
const wrapper = mount(MyComponent)
expect(wrapper.html()).toMatchSnapshot()
})
特点:
第一次测试生成快照文件
后续测试比较结果
快照需提交到版本控制
适合静态组件
注意:
快照不是替代品,需结合其他测试
动态内容可能导致失败
定期更新快照
55. 如何测试 Vuex store?
答案:
测试 Vuex store 方法:
单元测试 mutations:
javascript
test('increment mutation', () => {
const state = { count: 0 }
mutations.increment(state)
expect(state.count).toBe(1)
})
测试 getters:
javascript
test('evenOrOdd getter', () => {
const state = { count: 1 }
expect(getters.evenOrOdd(state)).toBe('odd')
})
测试 actions:
javascript
test('increment action', async () => {
const commit = jest.fn()
await actions.increment({ commit })
expect(commit).toHaveBeenCalledWith('increment')
})
集成测试:
javascript
const store = createStore(options)
store.dispatch('action')
expect(store.state).toEqual(...)
56. 什么是 SSR?Vue 中如何实现 SSR?
答案:
SSR (Server-Side Rendering) 是在服务器端生成 HTML 发送到客户端。
Vue 实现方式:
使用 Nuxt.js 框架
手动配置:
vue-server-renderer
Express/Koa 服务器
Webpack 配置
优点:
更好的 SEO
更快的内容到达时间
对低端设备更友好
缺点:
开发复杂度高
服务器负载大
部分库需要特殊处理
- SSR 和 CSR 的区别是什么?
答案:
SSR (Server-Side Rendering):
在服务器生成完整 HTML
客户端"激活"交互
首屏加载快
SEO 友好
服务器压力大
CSR (Client-Side Rendering):
服务器返回空 HTML 和 JS
客户端渲染所有内容
首屏加载慢
SEO 不友好
服务器压力小
混合方案:
预渲染
部分 SSR
渐进式激活
- Vue SSR 的性能优化手段有哪些?
答案:
SSR 优化手段:
代码层面:
避免单例状态
使用 bundleRenderer
组件级别缓存
减少序列化数据
基础设施:
使用 microcache
负载均衡
CDN
流式渲染
其他:
延迟加载非关键组件
预取数据
服务端压缩
59. 什么是 hydration?SSR 中如何处理?
答案:
hydration 是客户端 Vue 接管服务器渲染的静态 HTML 并使其交互的过程。
处理要点:
客户端和服务器输出必须匹配
避免 hydration 不匹配警告
特殊处理客户端特有代码:
if (process.client) {
// 只在客户端执行的代码
}
使用 <ClientOnly> 组件包装客户端特有内容
注意生命周期钩子调用时机
60. 如何处理 SSR 中的身份认证?
答案:
SSR 认证处理:
Cookie 认证:
服务器自动发送 cookie
适合传统 session
Token 认证:
服务器通过 initialState 传递 token
客户端存储 token
双重验证:
服务器检查 session
客户端检查 token
注意:
避免在服务器上使用 localStorage
处理异步用户状态
统一客户端和服务端状态
61. Vue CLI 和 Vite 的区别是什么?
答案:
Vue CLI:
基于 Webpack
功能全面
配置复杂
启动和热更新较慢
适合复杂项目
Vite:
基于原生 ES 模块
开发服务器极快
配置简单
生产打包使用 Rollup
适合现代浏览器项目
选择:
新项目推荐 Vite
已有 Vue CLI 项目可逐步迁移
62. 如何优化 Vue 应用的打包体积?
答案:
优化手段:
代码分割:
路由懒加载
组件异步加载
按需引入:
组件库按需导入
工具函数按需导入
压缩:
JS/CSS 压缩
图片压缩
Gzip/Brotli
分析:
webpack-bundle-analyzer
移除重复依赖
其他:
使用现代模式 (Vue CLI)
外部化大型库
63. 如何实现 Vue 应用的预渲染?
答案:
预渲染是在构建时生成静态 HTML 文件。
实现方式:
使用 prerender-spa-plugin:
javascript
new PrerenderSPAPlugin({
staticDir: path.join(__dirname, 'dist'),
routes: ['/', '/about'],
})
使用 Vue CLI 插件:
vue add prerender-spa
使用 Vite 插件:
import { vitePrerender } from 'vite-plugin-prerender'
适用场景:
营销页面
内容不常变化的页面
改善 SEO
64. 如何部署 Vue 应用到不同的环境?
答案:
部署方案:
静态部署:
Nginx/Apache
CDN
GitHub Pages
服务端渲染:
Node.js 服务器
Docker 容器
Serverless
现代部署:
Vercel
Netlify
Cloudflare Pages
环境变量管理:
.env 文件
Vue CLI 模式
Vite 环境变量
65. 如何处理 Vue 应用的跨域问题?
答案:
跨域解决方案:
开发环境:
配置 devServer.proxy (Vue CLI)
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
生产环境:
后端配置 CORS
Nginx 反向代理
API 网关
其他方案:
JSONP (仅 GET)
后端转发
浏览器插件临时解决
进阶概念
66. 什么是高阶组件?如何实现?
答案:
高阶组件 (HOC) 是接收组件并返回新组件的函数。
实现:
javascript
function withLoading(WrappedComponent) {
return {
data() {
return { isLoading: true }
},
mounted() {
setTimeout(() => {
this.isLoading = false
}, 1000)
},
render(h) {
if (this.isLoading) {
return h('div', 'Loading...')
}
return h(WrappedComponent, {
props: this.$props
})
}
}
}
使用:
javascript
const EnhancedComponent = withLoading(MyComponent)
Vue 3 替代方案:
组合式函数
渲染函数
插槽
67. 什么是递归组件?如何使用?
答案:
递归组件是调用自身的组件。
使用:
通过 name 选项递归:
javascript
<template>
<div>
<my-component v-if="condition" />
</div>
</template>
<script>
export default {
name: 'MyComponent'
}
</script>
通过组件引用递归:
javascript
import RecursiveComponent from './RecursiveComponent.vue'
export default {
components: {
RecursiveComponent
}
}
注意:
必须有终止条件
可能影响性能
适合树形结构数据
68. 什么是作用域插槽?使用场景是什么?
答案:
作用域插槽允许子组件向插槽传递数据。
使用:
javascript
<slot :item="item" :index="index"></slot>
javascript
<template v-slot:default="slotProps">
{{ slotProps.item }} - {{ slotProps.index }}
</template>
简写 (Vue 2.6+):
javascript
<template #default="{ item, index }">
{{ item }} - {{ index }}
</template>
场景:
数据列表组件
表格组件
任何需要灵活内容渲染的组件
69. Vue 3 的 createApp 和 new Vue() 有什么区别?
答案:
区别:
创建方式:
Vue 2: new Vue({...})
Vue 3: createApp({...})
全局 API:
Vue 2: 全局修改 Vue 原型
Vue 3: 应用实例作用域
配置:
Vue 2: 全局配置
Vue 3: 每个应用独立配置
挂载:
Vue 2: $mount()
Vue 3: mount()
示例:
// Vue 2
new Vue({ el: '#app' })
// Vue 3
createApp(App).mount('#app')
70. 什么是响应式丢失问题?如何解决?
答案:
响应式丢失是指解构或展开响应式对象时失去响应性。
常见场景:
解构 props:
javascript
const { foo } = this.props // 失去响应性
展开响应式对象:
return { ...reactiveObj } // 失去响应性
解决方案:
使用 toRefs:
javascript
const { foo } = toRefs(props)
保持引用:
javascript
return { reactiveObj }
使用 computed:
javascript
const foo = computed(() => props.foo)
71. Vue 应用常见的错误有哪些?如何捕获?
答案:
常见错误:
渲染错误
事件处理错误
生命周期钩子错误
异步操作错误
自定义指令错误
捕获方式:
全局错误处理:
javascript
app.config.errorHandler = (err, vm, info) => {
// 处理错误
}
生命周期钩子:
javascript
errorCaptured(err, vm, info) {
// 捕获子孙组件错误
}
异步错误:
javascript
window.addEventListener('unhandledrejection', e => {})
第三方监控:
Sentry
Bugsnag
### 72. 如何处理 Vue 路由中的 404 页面?
答案:
处理方式:
通配符路由:
```javascript
{ path: '/:pathMatch(.*)*', component: NotFound }
导航守卫:
javascript
router.beforeEach((to, from, next) => {
if (!to.matched.length) {
next('/404')
} else {
next()
}
})
服务器配置 (history 模式):
location / {
try_files $uri $uri/ /index.html;
}
73. Vue 3 的 effectScope 是什么?如何使用?
答案:
effectScope 是 Vue 3.2+ 特性,用于组织和管理 effect。
使用:
javascript
import { effectScope, reactive, watch } from 'vue'
const scope = effectScope()
scope.run(() => {
const state = reactive({ count: 0 })
watch(() => state.count, console.log)
state.count++
})
scope.stop() // 停止所有 effect
场景:
组件 setup 中管理 effect
可组合的函数中
测试时清理 effect
74. 如何处理 Vue 中的内存泄漏?
答案:
内存泄漏常见原因:
全局变量
未清除的定时器/事件监听
未卸载的第三方库
闭包
未清理的 Vue 相关资源
解决方案:
组件卸载时清理:
javascript
onUnmounted(() => {
clearInterval(timer)
eventBus.off('event', handler)
})
避免意外全局变量
使用弱引用 (WeakMap/WeakSet)
使用内存分析工具定位
75. Vue 3 的 Suspense 如何处理错误?
答案:
Suspense 错误处理:
使用 onErrorCaptured:
javascript
onErrorCaptured((err) => {
error.value = err
return false // 阻止错误继续向上传播
})
错误边界组件:
javascript
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div v-if="error">{{ error.message }}</div>
<div v-else>Loading...</div>
</template>
</Suspense>
全局错误处理:
javascript
app.config.errorHandler = (err) => {
// 处理 Suspense 错误
}
76. Vue 3 中如何更好地使用 TypeScript?
答案:
最佳实践:
使用 defineComponent:
javascript
import { defineComponent } from 'vue'
export default defineComponent({
// 类型推断
})
类型化 props:
javascript
props: {
message: {
type: String as PropType<string>,
required: true
}
}
类型化 ref:
javascript
const count = ref<number>(0)
类型化事件:
javascript
const emit = defineEmits<{
(e: 'update', value: string): void
}>()
类型化 computed:
const double = computed<number>(() => count.value * 2)
77. 如何为 Vue 组件定义 TypeScript 类型?
答案:
定义组件类型:
javascript
import { DefineComponent } from 'vue'
interface Props {
title: string
count?: number
}
interface Emits {
(e: 'update', value: string): void
}
const MyComponent: DefineComponent<Props, {}, {}, {}, {}, {}, {}, Emits> = {
props: {
title: String,
count: Number
},
emits: ['update'],
setup(props, { emit }) {
// 类型推断可用
}
}
Vue 3.3+ 更简洁:
javascript
defineProps<{
title: string
count?: number
}>()
defineEmits<{
update: [value: string]
}>()
78. 如何为 Vuex/Pinia 添加 TypeScript 支持?
答案:
Vuex 类型支持:
javascript
interface State {
count: number
}
const store = createStore<State>({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++ // 类型推断
}
}
})
Pinia 类型支持:
javascript
export const useStore = defineStore('main', {
state: (): State => ({
count: 0
}),
actions: {
increment() {
this.count++ // 类型推断
}
}
})
组件中使用:
javascript
const store = useStore()
store.count // 类型安全
79. 如何为自定义 hook 添加类型?
答案:
自定义 hook 类型:
javascript
import { ref, Ref } from 'vue'
interface UseCounterOptions {
initialValue?: number
step?: number
}
interface UseCounterReturn {
count: Ref<number>
increment: () => void
decrement: () => void
}
export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
const count = ref(options.initialValue || 0)
function increment() {
count.value += options.step || 1
}
function decrement() {
count.value -= options.step || 1
}
return {
count,
increment,
decrement
}
}
80. Vue 3 中如何类型化全局属性和方法?
答案:
类型化全局属性:
javascript
// main.ts
app.config.globalProperties.$filters = {
formatDate(date: Date): string {
return date.toLocaleDateString()
}
}
// 类型声明
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$filters: {
formatDate: (date: Date) => string
}
}
}
// 组件中使用
const { proxy } = getCurrentInstance()
proxy.$filters.formatDate(new Date())
81. 如何实现权限控制系统?
答案:
权限控制实现方案:
路由级别:
javascript
{
path: '/admin',
meta: { requiresAdmin: true }
}
router.beforeEach((to) => {
if (to.meta.requiresAdmin && !user.isAdmin) {
return '/login'
}
})
组件级别:
javascript
<template>
<admin-panel v-if="user.isAdmin" />
</template>
指令级别:
javascript
Vue.directive('permission', {
inserted(el, binding) {
if (!checkPermission(binding.value)) {
el.parentNode.removeChild(el)
}
}
})
动态菜单:
根据权限生成菜单
后端返回可访问路由
82. 如何实现多主题切换功能?
答案:
主题切换实现:
CSS 变量方案:
javascript
:root {
--primary-color: #42b983;
}
.theme-dark {
--primary-color: #333;
}
function toggleTheme() {
document.body.classList.toggle('theme-dark')
}
类名切换:
vue
<div :class="[themeClass]"></div>
动态样式表:
javascript
function loadTheme(themeName) {
const link = document.createElement('link')
link.href = `/themes/${themeName}.css`
link.rel = 'stylesheet'
document.head.appendChild(link)
}
状态管理:
将主题信息存储在 Vuex/Pinia
持久化到 localStorage
83. 如何实现全局加载状态?
答案:
全局加载状态实现:
使用状态管理:
vue
// store
state: { isLoading: false },
mutations: {
SET_LOADING(state, value) {
state.isLoading = value
}
}
// 组件
<loading-spinner v-if="$store.state.isLoading" />
使用 provide/inject:
// 根组件
provide('isLoading', ref(false))
// 子组件
const isLoading = inject('isLoading')
使用事件总线:
// 请求拦截器
axios.interceptors.request.use(config => {
eventBus.emit('loading', true)
return config
})
组合式函数:
javascript
export function useLoading() {
const isLoading = ref(false)
function withLoading(fn) {
return async (...args) => {
isLoading.value = true
try {
await fn(...args)
} finally {
isLoading.value = false
}
}
}
return { isLoading, withLoading }
}
84. 如何实现表单验证系统?
答案:
表单验证方案:
使用 Vuelidate:
vue
import { required, email } from '@vuelidate/validators'
export default {
setup() {
return {
v$: useVuelidate()
}
},
data() {
return { email: '' }
},
validations: {
email: { required, email }
}
}
使用 VeeValidate:
vue
<Form @submit="onSubmit">
<Field name="email" rules="required|email" />
<ErrorMessage name="email" />
</Form>
自定义验证:
vue
function validate(form) {
const errors = {}
if (!form.name) errors.name = 'Required'
return errors
}
异步验证:
javascript
async function checkEmailUnique(email) {
const res = await api.checkEmail(email)
return res.available
}
85. 如何实现拖拽排序功能?
答案:
拖拽排序实现:
使用 SortableJS:
vue
import Sortable from 'sortablejs'
onMounted(() => {
Sortable.create(el, {
onEnd: (e) => {
// 处理排序
}
})
})
使用 Vue Draggable:
vue
<draggable v-model="list" @end="onDragEnd">
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</draggable>
原生实现:
javascript
function handleDragStart(e) {
e.dataTransfer.setData('text/plain', e.target.id)
}
function handleDrop(e) {
const id = e.dataTransfer.getData('text/plain')
// 重新排序
}
移动端支持:
使用 touch 事件
使用 Hammer.js 等库
86. 如何设计大型 Vue 应用的目录结构?
答案:
推荐目录结构:
src/
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── ui/ # 基础UI组件
│ └── ... # 其他组件
├── composables/ # 组合式函数
├── stores/ # 状态管理
├── router/ # 路由配置
├── views/ # 页面组件
├── services/ # API服务
├── utils/ # 工具函数
├── styles/ # 全局样式
├── types/ # 类型定义
├── App.vue # 根组件
└── main.ts # 入口文件
模块化方案:
按功能模块划分:
src/modules/
├── auth/
│ ├── components/
│ ├── store/
│ └── services/
└── user/
├── components/
├── store/
└── services/
按业务领域划分
混合方式
87. 如何实现微前端架构中的 Vue 应用?
答案:
Vue 微前端方案:
使用 qiankun:
javascript
// 主应用
registerMicroApps([
{
name: 'vue-app',
entry: '//localhost:7101',
container: '#subapp',
activeRule: '/vue'
}
])
// 子应用
export async function mount(props) {
render(props)
}
使用 Module Federation:
javascript
// webpack 配置
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js'
}
})
其他方案:
single-spa
iframe
关键点:
样式隔离
状态隔离
路由协调
公共依赖
88. 如何设计可复用的组件库?
答案:
组件库设计要点:
组件设计原则:
单一职责
可组合
明确的接口
良好的文档
技术实现:
使用 Vue CLI/Vite 打包
支持按需导入
类型定义
主题定制
文档:
Storybook
示例代码
API 文档
发布:
npm 包
版本管理
changelog
测试:
单元测试
可视化测试
端到端测试
89. 如何实现 Vue 应用的国际化?
答案:
国际化方案:
使用 vue-i18n:
vue
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'en',
messages: {
en: { welcome: 'Welcome' },
zh: { welcome: '欢迎' }
}
})
app.use(i18n)
组件中使用:
vue
<p>{{ $t('welcome') }}</p>
动态切换语言:
i18n.global.locale = 'zh'
高级功能:
懒加载语言包
复数处理
日期/数字格式化
90. 如何实现 Vue 和原生应用的混合开发?
答案:
混合开发方案:
WebView 嵌入:
原生应用内嵌 WebView
Vue 应用适配移动端
原生桥接:
javascript
// Android
window.androidBridge.callNativeMethod()
// iOS
window.webkit.messageHandlers.nativeHandler.postMessage()
使用 Capacitor:
javascript
import { Plugins } from '@capacitor/core'
const { Camera } = Plugins
async takePhoto() {
const image = await Camera.getPhoto()
}
其他方案:
Cordova
NativeScript-Vue
Weex
91. Vue 3 的编译器优化有哪些?
答案:
Vue 3 编译器优化:
静态节点提升:
将静态节点提升到渲染函数外
避免重复创建 VNode
补丁标志:
标记动态节点类型
减少 diff 时需要比较的内容
区块树:
将动态节点按结构划分区块
减少需要追踪的动态节点数量
缓存事件处理函数:
避免不必要的重新渲染
更快的 SSR 渲染
92. Vue 3 的静态提升是什么原理?
答案:
静态提升原理:
编译时分析模板:
识别完全静态的节点
识别只有 class/style 动态的节点
提升静态节点:
const _hoisted_1 = createVNode("div", null, "static content")
function render() {
return _hoisted_1
}
运行时直接复用:
跳过这些节点的 diff/patch
大幅提升更新性能
效果:
减少 VNode 创建开销
减少内存占用
提高渲染性能
93. Vue 3 的区块树优化是什么?
答案:
区块树 (Block Tree) 优化:
将模板划分为区块:
每个区块包含动态节点
静态内容作为整体处理
动态节点收集:
编译时收集动态节点引用
形成扁平数组结构
更新时:
直接遍历动态节点数组
跳过静态内容比较
优势:
减少虚拟 DOM 树遍历
更精确的更新目标
提高 diff 效率
94. Vue 3 的按需编译是什么?如何实现?
答案:
按需编译原理:
基于 ES 模块的 tree-shaking:
只打包实际使用的代码
移除未使用的功能
编译器优化:
根据模板实际使用的功能生成代码
例如不使用 v-model 就不编译相关代码
组合式 API 设计:
按需导入功能函数
更好的 tree-shaking
实现方式:
使用 Vite/Rollup
配置优化选项:
javascript
// vite.config.js
export default {
build: {
rollupOptions: {
treeshake: true
}
}
}
95. Vue 3 的 CSS 变量注入是什么?如何使用?
答案:
CSS 变量注入允许在 JS 中定义 CSS 变量并在样式中使用。
使用:
javascript
<script setup>
import { ref } from 'vue'
const color = ref('red')
</script>
<template>
<div class="text">Hello</div>
</template>
<style>
.text {
color: v-bind(color);
}
</style>
编译为:
javascript
.text {
color: var(--xxxxxx);
}
原理:
编译时将 v-bind 转换为 CSS 变量
运行时动态更新变量值
优势:
更好的 JS-CSS 集成
动态主题支持
类型安全
综合问题
96. Vue 3 相比 React 有哪些优势和劣势?
答案:
Vue 3 优势:
更简单的学习曲线
更灵活的模板和 JSX 支持
更精细的响应式系统
更小的运行时体积
更好的性能优化
组合式 API 更灵活
React 优势:
更大的生态系统
更丰富的第三方库
更成熟的移动端方案
更灵活的渲染目标
更早引入的并发特性
选择考虑:
偏好模板语法选 Vue
需要最大灵活性选 React
性能敏感场景 Vue 可能更优
大型团队 React 可能更合适
97. Vue 3 和 Svelte 的主要区别是什么?
答案:
主要区别:
编译策略:
Svelte 编译为高效命令式代码
Vue 保留运行时
响应式:
Svelte 使用编译时响应式
Vue 使用运行时响应式系统
状态管理:
Svelte 使用简单的 store
Vue 提供 Vuex/Pinia
生态系统:
Vue 有更成熟的生态系统
Svelte 更轻量
学习曲线:
Svelte 更简单
Vue 提供更多高级功能
98. Vue 3 未来的发展方向是什么?
答案:
Vue 未来方向:
更好的 TypeScript 支持
更强大的编译时优化
更小的运行时体积
更好的开发工具体验
更完善的 SSR 支持
更强大的组合式 API
可能的并发渲染支持
更友好的移动端方案
社区趋势:
Vite 成为标配
Pinia 取代 Vuex
组合式 API 成为主流
更多基于编译的优化
99. 如何成为 Vue 高级开发者?
答案:
进阶路径:
深入理解核心原理:
响应式系统
虚拟 DOM
编译器
掌握高级特性:
自定义渲染器
编译器宏
源码定制
性能优化专家:
渲染性能
打包优化
内存管理
架构设计能力:
大型应用架构
微前端
组件库设计
社区贡献:
参与开源
编写插件
分享经验
100. 如何设计一个 Vue 技术架构面试题?
答案:
好的 Vue 架构面试题应包含:
技术选型:
Vue 2 vs Vue 3
状态管理方案
构建工具选择
应用结构:
目录组织
代码拆分
模块划分
性能考虑:
加载性能
运行时性能
内存使用
扩展性:
新功能添加
团队协作
长期维护
实际场景:
给定业务需求
特定约束条件
权衡取舍