Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)

🔥Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)

系列最后一篇。把这 30 道题吃透,Vue 面试你可以反向输出。


写在前面

这是 Vue 面试系列的最终篇 ([第 1 篇:核心基础](#第 1 篇:核心基础) | [第 2 篇:Composition API 进阶](#第 2 篇:Composition API 进阶) | [第 3 篇:Router/Pinia/响应式源码](#第 3 篇:Router/Pinia/响应式源码))。

前面三篇是"知识输入",这一篇是**"面试输出"**:

  • SSR 到底解决什么问题,Nuxt 3 怎么用
  • 测试怎么写(很多候选人只会说"写过"但不会写)
  • 30 道高频面试题的完整标准答案------基础题、进阶题、源码题、实战题四个梯队

适合谁看?

  • ✅ 面试前一周做最后冲刺的同学(第 4 节的 30 道题就是模拟考卷)
  • ✅ 想了解 SSR / 测试但一直没动手实践的人
  • ✅ 想收藏一份 Vue 面试题库随时翻的人

阅读建议 :第 4 节建议合上文章自己先答一遍,再对照答案找差距。


📑 目录

  1. 服务端渲染(SSR)
  2. 测试
  3. [Vue 生态工具](#Vue 生态工具)
  4. 最佳实践
  5. [高频面试题精选(30 道标准答案)](#高频面试题精选(30 道标准答案))

📌 附:全系列知识点总索引


26. 服务端渲染(SSR)

26.1 先想清楚:为什么需要 SSR

复制代码
CSR(客户端渲染)的问题:
浏览器 → 下载 HTML(空壳) → 下载 JS → 执行 JS → 渲染页面
                          ↑ 这一段用户看到的是白屏

SSR(服务端渲染):
浏览器 → 下载 HTML(已填充内容) → 立即显示 → 下载 JS → 接管(hydration)
                              ↑ 首屏内容直出

收益 :SEO 友好(爬虫拿到完整内容)、首屏时间大幅缩短、弱网设备体验好。

代价 :服务器算力开销、开发复杂度上升(注意窗口对象、生命周期差异)、mounted 之前的钩子外的环境差异。

判断口诀:内容型站点(官网、博客、电商)需要 SEO → 上 SSR;后台管理系统、登录后的应用 → CSR 足够。

26.2 Nuxt 3 实战速览

typescript 复制代码
// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true,                 // 开 SSR(false 则变 SPA)
  pages: true,               // pages/ 目录自动路由
  components: true,          // 组件自动导入
  modules: ['@pinia/nuxt'],  // Pinia 官方模块
  runtimeConfig: {
    apiSecret: '',                    // 仅服务端可见
    public: { apiBase: '/api' }       // 客户端可见
  }
});
vue 复制代码
<!-- pages/index.vue ------ 约定式路由 + 服务端取数 -->
<script setup>
// useFetch:SSR 阶段在服务端请求,客户端 hydration 复用结果,不重复请求
const { data, pending, error } = await useFetch('/api/articles');

// SEO
useHead({
  title: '首页',
  meta: [{ name: 'description', content: '页面描述' }]
});

// 页面级中间件 + 指定布局
definePageMeta({ middleware: 'auth', layout: 'default' });
</script>
typescript 复制代码
// server/api/articles.ts ------ 服务端 API(Nitro,可部署为 Serverless 函数)
export default defineEventHandler(async (event) => {
  const query = getQuery(event);
  return await getArticles(query);
});

💬 面试题精选

Q1:SSR 和 CSR 的区别?什么时候用 SSR?

答(决策导向):

特性 SSR CSR
首屏 服务端直出 HTML,快 下载 JS 后渲染,慢
SEO ✅ 爬虫拿到完整内容 ❌ 需要额外预渲染
服务器成本
开发复杂度 高(生命周期、环境判断)
适用 内容站、SEO 刚需 后台系统、Web 应用

什么时候用:有 SEO 需求/首屏性能要求高(移动端弱网)→ SSR 或 SSG;纯交互型应用(管理后台)→ CSR。

💡 加分话术 :还有第三条路 SSG(静态生成)------构建时把页面渲染成静态 HTML,没有服务器渲染开销又保留 SEO,博客、文档站首选(VitePress/Nuxt SSG)。选型上我会先看"页面是否依赖实时数据 + 是否需要 SEO"这两个维度。

Q2:SSR 时数据是怎么获取的?为什么 hydration 不会重复请求?

答:Nuxt 的 useFetch/useAsyncData服务端渲染阶段 就发起请求,结果序列化内联进 HTML(__NUXT_DATA__ 脚本);客户端 hydration 时读取这份内联数据直接复用,不会重新发请求。如果客户端导航到新页面,则走正常的客户端请求。

💡 坑点提醒:SSR 阶段没有 window/document ,访问浏览器 API 要放在 onMounted 里或 import.meta.client 判断,否则服务端报错。


27. 测试

💡 面试被问"写过测试吗",答"写过"和答"写过,我是这么组织的"是两回事。这节给你能讲出来的代码。

27.1 组件测试(Vitest + Vue Test Utils)

typescript 复制代码
// components/Counter.spec.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';

describe('Counter.vue', () => {
  it('正确渲染初始值', () => {
    const wrapper = mount(Counter, { props: { initialCount: 0 } });
    expect(wrapper.text()).toContain('0');
  });

  it('点击按钮递增并发出事件', async () => {
    const wrapper = mount(Counter);
    await wrapper.find('button.increment').trigger('click');

    // 断言 DOM 更新
    expect(wrapper.find('.count').text()).toBe('1');
    // 断言事件( emitted() 拿到所有触发记录 )
    expect(wrapper.emitted('update')![0]).toEqual([1]);
  });

  it('渲染插槽内容', () => {
    const wrapper = mount(Counter, {
      slots: { default: '<span>自定义内容</span>' }
    });
    expect(wrapper.html()).toContain('自定义内容');
  });

  it('mock 子组件,专注测当前组件', () => {
    const wrapper = mount(MyPage, {
      global: { stubs: { RouterLink: true, HeavyChart: true } }
    });
  });
});

💡 三个最常用的断言姿势:wrapper.find() 查 DOM、trigger('click') 触发事件(注意要 await)、wrapper.emitted() 验证事件。

27.2 Pinia 测试

typescript 复制代码
// stores/counter.spec.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { setActivePinia, createPinia } from 'pinia';
import { useCounterStore } from './counter';

describe('Counter Store', () => {
  beforeEach(() => {
    // ⚠️ 每个用例前创建全新 pinia 实例,保证用例间不互相污染
    setActivePinia(createPinia());
  });

  it('increment 生效', () => {
    const store = useCounterStore();
    store.increment();
    expect(store.count).toBe(1);
  });

  it('getter 计算正确', () => {
    const store = useCounterStore();
    store.count = 5;
    expect(store.doubleCount).toBe(10);
  });
});

💬 面试题精选

Q1:Vue 项目的测试策略怎么设计?

答(测试金字塔):

  1. 单元测试(最多):工具函数、Composables、Store------Vitest,毫秒级
  2. 组件测试:关键交互组件的渲染/事件/emit------Vue Test Utils
  3. E2E 测试(最少但关键):登录、下单等核心链路------Cypress / Playwright

💡 加分话术 :我的实践是"Store 和 Composables 必测(它们是业务逻辑核心),展示组件抽几个交互复杂的测,核心链路用 E2E 兜底"。测试的价值在于重构时的安全感,不是覆盖率数字。

Q2:mountshallowMount 的区别?

答:mount 完整挂载组件包括子组件;shallowMount 把子组件替换成占位 stub,只测当前组件本身。现在 VTU 2.x 推荐用 mount + global.stubs 显式声明要 stub 的组件,语义更清晰。


28. Vue 生态工具

💡 这节不用背,知道"有什么轮子"即可,面试时提到是项目经验加分项。

28.1 VueUse:组合式函数工具库(必装)

typescript 复制代码
import {
  useMouse, useLocalStorage, useDark, useDebounceFn,
  useIntersectionObserver, useEventListener
} from '@vueuse/core';

// 自动处理监听和卸载的工具,比自己写少踩 80% 的坑
const { x, y } = useMouse();
const theme = useDark();                          // 暗黑模式一行搞定
const keyword = ref('');
const debouncedSearch = useDebounceFn(search, 300);

// 元素进入视口(图片懒加载、无限滚动)
useIntersectionObserver(el, ([{ isIntersecting }]) => {
  if (isIntersecting) loadMore();
});

// 事件监听自动随组件卸载移除
useEventListener(window, 'resize', handler);

28.2 表单验证 VeeValidate + Zod

vue 复制代码
<script setup>
import { useForm, useField } from 'vee-validate';
import { z } from 'zod';
import { toTypedSchema } from '@vee-validate/zod';

const schema = toTypedSchema(z.object({
  email: z.string().email('请输入有效邮箱'),
  password: z.string().min(6, '密码至少 6 位')
}));

const { handleSubmit, errors } = useForm({ validationSchema: schema });
const { value: email } = useField('email');
const { value: password } = useField('password');

const onSubmit = handleSubmit((values) => { /* values 类型安全 */ });
</script>

<template>
  <form @submit="onSubmit">
    <input v-model="email" />
    <span>{{ errors.email }}</span>
    <button type="submit">提交</button>
  </form>
</template>

28.3 其他生态速览

  • UI 库:Element Plus(中后台)、Vant(移动端)、Vuetify(Material)
  • 国际化 :vue-i18n(t('message.hello', { name })
  • CSS 方案:UnoCSS / Tailwind(原子化)、SCSS modules
  • 文档:VitePress(Vue 官方文档站就是)

💡 面试话术:组件库选型我会看三点------TS 类型支持、按需引入/Tree-shaking 能力、主题定制体系(CSS 变量方案优先,方便换肤)。


29. 最佳实践

29.1 项目结构

复制代码
src/
├── api/              # API 请求层(按业务模块拆分)
│   └── modules/
├── components/
│   ├── common/       # 通用组件(无业务)
│   └── business/     # 业务组件
├── composables/      # 组合式函数
├── directives/       # 自定义指令
├── layouts/          # 布局组件
├── router/           # 路由
├── stores/           # Pinia
├── styles/           # 全局样式/主题变量
├── types/            # TS 类型
└── utils/            # 纯工具函数

29.2 组件设计原则(被问"如何设计组件"时按这四点答)

vue 复制代码
<script setup lang="ts">
// ① 单一职责:一个组件只做一件事,做大了就拆
// ② Props:类型明确 + 默认值 + 校验
interface Props {
  title: string;
  type?: 'primary' | 'secondary';
}
const props = withDefaults(defineProps<Props>(), { type: 'primary' });

// ③ 事件:动词命名,参数语义明确
const emit = defineEmits<{
  (e: 'update', value: string): void;
  (e: 'delete', id: number): void;
}>();

// ④ 插槽:提供合理的默认内容,使用者不传也能用
</script>

<template>
  <div class="card">
    <slot name="header"><h3>{{ title }}</h3></slot>
    <slot>默认内容</slot>
  </div>
</template>

29.3 错误处理体系

typescript 复制代码
// main.ts:全局兜底(配合 Sentry 等上报)
app.config.errorHandler = (err, instance, info) => {
  reportError(err);
};

// 组件级:onErrorCaptured 做局部兜底(如表单区错误不拖垮整页)
onErrorCaptured((err) => {
  showToast('该模块出错了');
  return false;  // 阻止继续向上传播
});

// API 层:统一拦截,401 跳登录、其他弹 toast
request.interceptors.response.use(
  res => res.data,
  err => {
    if (err.response?.status === 401) router.push('/login');
    return Promise.reject(err);
  }
);

30. 高频面试题精选

🎯 使用方式:合上文自己先答,再对答案。标 ⭐ 的是出现率最高的。

第一梯队:基础题(必对,错一道都危险)

Q1:Vue 的核心特性有哪些?

响应式数据绑定、组件化、虚拟 DOM、指令系统、模板语法、渐进式框架。(展开见第 1 篇 §1)

Q2:⭐ v-ifv-show 的区别?

v-if 条件渲染(false 时销毁组件,切换开销大);v-show 仅切 display(初始渲染开销大,切换快)。频繁切换用 v-show,条件稳定用 v-if。

Q3:⭐ Vue 2 和 Vue 3 的主要区别?

响应式(defineProperty→Proxy)、Composition API、编译优化+Tree-shaking、Fragment、原生 TS。(展开见第 1 篇 §3)

Q4:⭐ 为什么 v-for 要加 key?index 做 key 有什么问题?

key 是节点身份标识,帮助 diff 复用/移动节点。index 在顺序变化时导致组件状态错乱(输入框串位、选中态错位)。用唯一 id。(第 1 篇 §6)

第二梯队:进阶题(决定面试评级)

Q5:⭐ Composition API 解决了什么问题?

Options API 中同一逻辑代码分散、Mixins 复用有命名冲突和来源不清问题;Composition API 按逻辑功能组织,Composables 实现干净复用。(第 2 篇 §12)

Q6:⭐ Pinia 和 Vuex 的区别?

去掉 mutations(action 直接改 state)、TS 原生支持、扁平模块化、体积 ~1KB。Pinia 是官方钦定的 Vuex 继任者。(第 3 篇 §21)

Q7:⭐ watchwatchEffect 的区别?

watch 显式指定源、可拿旧值、懒执行;watchEffect 自动追踪、立即执行、无旧值。副作用清理都用 onCleanup 防竞态。(第 2 篇 §13)

Q8:组件通信有哪些方式?

props/emit(父子)、provide/inject(跨层)、Pinia(全局)、mitt(任意)。加分项:按场景选型 + 说明 Event Bus 在大型项目难追踪的缺陷。(第 1 篇 §10)

第三梯队:源码题(拉开差距)

Q9:⭐⭐ Vue 3 响应式原理,能手写吗?

Proxy 拦截 + track 依赖收集 + trigger 派发更新,WeakMap 存依赖防泄漏,惰性代理提升初始化性能。建议默写 第 3 篇 §23.2 的迷你实现。

Q10:⭐ Vue 的虚拟 DOM 和 Diff 算法?

数据变化生成新 vnode → 与旧 vnode 比对 → 最小化更新真实 DOM。Vue 3 的优化:Patch Flags 标记动态内容只比对动态部分、Block Tree 跳过静态、最长递增子序列优化移动。意义:用 JS 计算换 DOM 操作(DOM 操作才是性能瓶颈)。

Q11:Vue 3 编译时优化有哪些?

静态提升(hoistStatic)、Patch Flags、Block Tree、静态节点字符串化、事件监听缓存------运行时快的原因大头在编译期。

Q12:为什么 ref 需要 .value?

ref 用包装对象承载任意类型(Proxy 只能代理对象),拦截发生在 .value 这一层。模板中自动解包。(第 1/3 篇)

第四梯队:实战题(体现项目深度)

Q13:⭐ 如何实现前端权限控制?

两种方案:① 路由 meta + 全局守卫做拦截;② 登录后按角色 addRoute 动态注册(路由表里没有,更安全),配合 404 兜底。实际项目两者结合。(第 3 篇 §19)

Q14:⭐ Vue 项目性能优化有哪些?

四层框架:加载层(懒加载/按需/图片优化/CDN)、运行时层(虚拟滚动/防抖节流/shallowRef/markRaw/v-memo)、更新层(合理 key/组件拆细/computed)、体验层(keep-alive/骨架屏/SSR)。先测量再优化。(第 3 篇 §24)

Q15:如何设计一个通用组件/组件库?

单一职责;Props 类型+默认值+校验;事件动词命名;插槽给默认内容;主题用 CSS 变量;Storybook 写文档;单测+快照;构建支持 Tree-shaking 按需引入;语义化版本管理。

Q16:nextTick 的原理和使用场景?

Vue 更新是异步批量的(微任务队列),数据变了 DOM 不会立即更新。await nextTick() 后在回调里拿最新 DOM。场景:改数据后需要测量/操作新 DOM。

Q17:说说 Vue 的 hydration(水合)?

SSR 页面拿到的是静态 HTML,客户端 JS 加载后需要"激活"它------对比服务端 HTML 与虚拟 DOM,挂上事件监听和响应式,让它变成可交互的。hydration 失败常见原因:服务端与客户端渲染结果不一致。

Q18:Vuex/Pinia 的持久化怎么做?

store.$subscribe 订阅变化写 localStorage(注意只存可序列化数据、敏感数据不要存、可配 pinia-plugin-persistedstate)。

Q19:海量数据渲染怎么优化?

虚拟滚动(定高用 useVirtualList,不定高用动态高度缓存);分页/无限滚动加载;shallowRef 存数据避开深层代理;web worker 做数据处理。

Q20:封装 axios 你做了哪些事?

baseURL 环境区分、请求拦截注入 token、响应拦截统一拆 data/错误处理(401 跳登录/刷新 token)、取消重复请求(防抖队列)、loading 与错误 toast 全局开关。(第 4 篇附录有完整代码参考)

Q21-30 :路由守卫完整执行顺序、SSR 与 SSG 选型、keep-alive 原理、Teleport 场景、作用域插槽与无渲染组件、自定义指令场景、跨域与代理配置、v-model 组件封装、错误处理体系、组件库按需引入原理------全部覆盖在前三篇对应章节,此处不重复展开,面试前请按速查表回查。


全系列知识点总索引

篇目 覆盖内容
[第 1 篇:核心基础](#篇目 覆盖内容 第 1 篇:核心基础 模板语法、响应式、computed/watch、指令、组件通信、生命周期 第 2 篇:进阶 Composition API、侦听器、异步组件/keep-alive/Teleport、自定义指令、插槽、动画、Provide/Inject 第 3 篇:硬核 Router、Vuex、Pinia、Vue3 新特性、响应式源码手写、性能优化、TypeScript 第 4 篇:收官(本文) SSR/Nuxt、测试、生态工具、最佳实践、30 道面试题) 模板语法、响应式、computed/watch、指令、组件通信、生命周期
[第 2 篇:进阶](#篇目 覆盖内容 第 1 篇:核心基础 模板语法、响应式、computed/watch、指令、组件通信、生命周期 第 2 篇:进阶 Composition API、侦听器、异步组件/keep-alive/Teleport、自定义指令、插槽、动画、Provide/Inject 第 3 篇:硬核 Router、Vuex、Pinia、Vue3 新特性、响应式源码手写、性能优化、TypeScript 第 4 篇:收官(本文) SSR/Nuxt、测试、生态工具、最佳实践、30 道面试题) Composition API、侦听器、异步组件/keep-alive/Teleport、自定义指令、插槽、动画、Provide/Inject
[第 3 篇:硬核](#篇目 覆盖内容 第 1 篇:核心基础 模板语法、响应式、computed/watch、指令、组件通信、生命周期 第 2 篇:进阶 Composition API、侦听器、异步组件/keep-alive/Teleport、自定义指令、插槽、动画、Provide/Inject 第 3 篇:硬核 Router、Vuex、Pinia、Vue3 新特性、响应式源码手写、性能优化、TypeScript 第 4 篇:收官(本文) SSR/Nuxt、测试、生态工具、最佳实践、30 道面试题) Router、Vuex、Pinia、Vue3 新特性、响应式源码手写、性能优化、TypeScript
第 4 篇:收官(本文) SSR/Nuxt、测试、生态工具、最佳实践、30 道面试题

📌 最后

四篇写完,这套 Vue 知识体系总算完整了。从模板语法到响应式源码,从组件通信到 SSR------收藏这一篇,等于收藏了整个系列。

  • 如果这个系列帮到了你,点赞 ❤️ + 收藏 ⭐ 是对创作者最大的鼓励
  • 评论区可以留下你面试遇到的新题,我会持续补充到题库中(更新日志见下)
  • 接下来想看什么系列?React 八股文 / 前端工程化 / 算法面试------评论区投票,票数高的优先写 🙏

附录:两个可以直接放进项目的工具封装

1. 通用的 loading 组合式函数

typescript 复制代码
// composables/useLoading.ts
import { ref } from 'vue';

export function useLoading(initial = false) {
  const loading = ref(initial);

  async function withLoading<T>(fn: () => Promise<T>): Promise<T> {
    loading.value = true;
    try {
      return await fn();
    } finally {
      loading.value = false;   // finally 保证异常时也能复位
    }
  }

  return { loading, withLoading };
}

// 使用:const { loading, withLoading } = useLoading();
// await withLoading(() => api.getList())

2. 封装好的 axios 实例(面试常问的"请求层")

typescript 复制代码
// utils/request.ts
import axios from 'axios';

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE,
  timeout: 10000
});

request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

request.interceptors.response.use(
  (response) => response.data,
  (error) => {
    if (error.response?.status === 401) {
      // 清 token 跳登录
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default request;
相关推荐
weixin_BYSJ19871 小时前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
Bs_MoneyMagnet1 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
驳是2 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea2 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持2 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者2 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn3 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
Bs_MoneyMagnet4 小时前
基于springboot+vue的心理咨询预约与随访平台的设计与实现 源码+文档
vue.js·spring boot·后端·spring·毕业设计·旅游·计算机毕业设计
计算机魔术师4 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端