Skip to content

Vue3 高频面试题(下) ​

56. Pinia 与 Vuex 的区别 ​

56.1 核心概念 ​

  • Vuex:包含 State、Getter、Mutation、Action、Module 等概念
  • Pinia:包含 State、Getter、Action 等概念,移除了 Mutation

56.2 类型支持 ​

  • Vuex:TypeScript 支持不够完善,需要额外配置
  • Pinia:原生支持 TypeScript,类型推断更准确

56.3 模块化 ​

  • Vuex:需要使用 Module 来组织代码
  • Pinia:每个 store 就是一个独立的模块,无需嵌套

56.4 调试 ​

  • Vuex:支持时间旅行调试
  • Pinia:支持 Vue DevTools,调试体验更好

56.5 状态修改 ​

  • Vuex:需要通过 Mutation 修改状态,Action 中提交 Mutation
  • Pinia:可以直接在 Action 中修改状态,无需 Mutation

57. Vue Router 4 ​

57.1 Vue Router 4 的新特性 ​

  • 组合式 API:支持 useRoute、useRouter 等组合式 API
  • TypeScript 支持:更好的 TypeScript 支持
  • 动态路由:更灵活的动态路由配置
  • 导航守卫:支持异步组件的导航守卫

57.2 useRoute 与 useRouter 的区别 ​

  • useRoute:获取当前路由信息
  • useRouter:获取路由实例,用于导航
javascript
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

console.log(route.path) // 当前路由路径
router.push('/home') // 导航到 home 页面

57.3 导航守卫的使用 ​

  • 全局守卫:beforeEach、afterEach
  • 路由守卫:beforeEnter
  • 组件守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

57.4 动态路由的实现 ​

  • 添加路由:router.addRoute()
  • 移除路由:router.removeRoute()
  • 查看路由:router.getRoutes()

57.5 路由懒加载 ​

  • 实现方式:使用 import() 函数动态导入组件
javascript
const Home = () => import('../views/Home.vue')

const routes = [
  { path: '/home', component: Home }
]

58. 服务端渲染 (SSR) ​

58.1 SSR 的优势 ​

  • SEO 优化:搜索引擎能更好地爬取页面内容
  • 首屏加载速度:减少客户端渲染时间
  • 用户体验:更快地看到页面内容

58.2 SSR 的实现方式 ​

  • Nuxt.js:基于 Vue 的 SSR 框架
  • Vite SSR:Vite 提供的 SSR 支持

58.3 SSR 与 CSR 的区别 ​

  • SSR:服务端渲染 HTML,客户端激活
  • CSR:客户端渲染 HTML,服务端只提供数据

58.4 SSR 的性能优化 ​

  • 缓存:使用缓存策略减少服务器压力
  • 预渲染:对静态页面进行预渲染
  • 按需加载:只渲染必要的组件

59. 性能优化 ​

59.1 编译时优化 ​

  • 静态提升:将静态节点提升到渲染函数外部
  • PatchFlag:标记动态节点,减少 diff 范围
  • Block Tree:将动态节点分组,提高 diff 效率

59.2 运行时优化 ​

  • 使用虚拟列表:处理大量数据的列表
  • 避免不必要的响应式转换:使用 markRaw 标记不需要响应式的对象
  • 合理使用计算属性:避免重复计算

59.3 组件优化 ​

  • 组件拆分:将复杂组件拆分为更小的组件
  • 异步组件:按需加载组件
  • KeepAlive:缓存组件实例,避免重复渲染

59.4 网络优化 ​

  • CDN 加速:使用 CDN 加速静态资源
  • 资源压缩:压缩 JS、CSS、图片等资源
  • HTTP/2:使用 HTTP/2 协议

60. 错误处理 ​

60.1 全局错误处理 ​

  • app.config.errorHandler:捕获全局错误
javascript
app.config.errorHandler = (err, instance, info) => {
  // 处理错误
  console.error(err)
}

60.2 组件错误处理 ​

  • onErrorCaptured:捕获组件树中的错误
javascript
import { onErrorCaptured } from 'vue'

setup() {
  onErrorCaptured((err, instance, info) => {
    // 处理错误
    console.error(err)
    return false // 阻止错误继续传播
  })
}

60.3 异步错误处理 ​

  • try/catch:捕获异步操作中的错误
javascript
try {
  const data = await fetchData()
} catch (error) {
  console.error(error)
}

61. TypeScript 支持 ​

61.1 Vue3 的 TypeScript 支持 ​

  • 原生支持:Vue3 原生支持 TypeScript
  • 类型推断:更好的类型推断
  • 组件类型:使用 defineComponent 定义组件类型

61.2 组合式 API 的类型支持 ​

  • setup 函数:自动推断返回值类型
  • ref/reactive:自动推断类型
  • props:使用 defineProps 定义 props 类型

61.3 自定义类型 ​

  • 接口定义:使用接口定义组件类型
typescript
interface User {
  name: string
  age: number
}

const user = ref<User>({ name: 'Vue', age: 3 })

62. 工具函数 ​

62.1 toRaw 的作用 ​

  • toRaw:获取响应式对象的原始对象
  • 使用场景:在不需要响应式的场景下使用原始对象,提高性能

62.2 isRaw 的作用 ​

  • isRaw:判断一个对象是否是原始对象

62.3 isRef 的作用 ​

  • isRef:判断一个值是否是 ref 对象

62.4 isReactive 的作用 ​

  • isReactive:判断一个对象是否是 reactive 对象

62.5 unref 的作用 ​

  • unref:获取 ref 对象的值,如果不是 ref 对象则直接返回

63. 边界情况处理 ​

63.1 响应式对象的循环引用 ​

  • 处理方式:Vue3 使用 WeakMap 存储响应式对象,自动处理循环引用
  • 注意事项:避免创建过多的循环引用,可能导致内存泄漏

63.2 大型响应式对象的性能问题 ​

  • 解决方案:
    • 使用 shallowRef/shallowReactive 避免深度响应式转换
    • 使用 markRaw 标记不需要响应式的对象
    • 拆分大型对象为多个小型响应式对象

63.3 组件的递归调用 ​

  • 实现方式:组件可以递归调用自己
  • 注意事项:需要设置递归终止条件,避免无限递归
vue
<template>
  <div>
    <div>{{ node.name }}</div>
    <tree-node v-if="node.children" :nodes="node.children" />
  </div>
</template>

<script setup>
const props = defineProps({
  nodes: Array
})
</script>

64. Vue3 的生态系统 ​

64.1 核心库 ​

  • vue:Vue3 核心库
  • vue-router:路由管理
  • pinia:状态管理
  • vue-test-utils:测试工具

64.2 构建工具 ​

  • vite:新一代前端构建工具
  • webpack:传统前端构建工具

64.3 UI 框架 ​

  • Element Plus:Element UI 的 Vue3 版本
  • Ant Design Vue:Ant Design 的 Vue3 版本
  • Vuetify:Material Design 风格的 UI 框架

65. Vue3 的未来发展 ​

65.1 组合式 API 的普及 ​

  • 组合式 API 将成为 Vue3 的主要开发方式
  • 更多的第三方库将支持组合式 API

65.2 性能优化 ​

  • Vue3 会持续优化性能
  • 更好的编译优化和运行时优化

65.3 生态系统的完善 ​

  • 更多的工具和库将支持 Vue3
  • 更好的开发者体验

66. 实践经验与技巧 ​

66.1 代码组织 ​

  • 按功能组织代码:将相关功能的代码放在一起
  • 使用自定义 Hooks:封装可复用的逻辑
  • 组件拆分:将复杂组件拆分为更小的组件

66.2 调试技巧 ​

  • 使用 Vue DevTools:调试 Vue 组件和响应式数据
  • 使用 console.log:打印关键数据
  • 使用断点:调试复杂逻辑

66.3 性能监控 ​

  • 使用 Performance API:监控页面性能
  • 使用 Vue DevTools 的性能面板:监控组件渲染性能

67. 常见易错点与解决方案 ​

67.1 忘记使用 .value ​

  • 问题:在 setup 函数中访问 ref 时忘记使用 .value
  • 解决方案:记住在 setup 函数中访问 ref 需要使用 .value

67.2 响应式对象的解构赋值 ​

  • 问题:解构响应式对象会失去响应式
  • 解决方案:使用 toRefs 保持响应式

67.3 深度监听的性能问题 ​

  • 问题:对大型对象使用深度监听会影响性能
  • 解决方案:避免对大型对象使用深度监听,只监听需要变化的属性

67.4 组件的无限渲染 ​

  • 问题:组件无限渲染
  • 解决方案:检查是否在渲染函数中修改了响应式数据

68. 大型项目架构设计 ​

68.1 项目结构 ​

  • 按功能划分:将项目按功能划分为多个模块
  • 公共组件:将公共组件放在单独的目录中
  • 工具函数:将工具函数放在单独的目录中

68.2 状态管理 ​

  • 使用 Pinia:使用 Pinia 管理全局状态
  • 模块化:将状态按模块划分

68.3 路由设计 ​

  • 嵌套路由:使用嵌套路由组织页面结构
  • 动态路由:使用动态路由处理参数
  • 懒加载:按需加载路由组件

68.4 权限管理 ​

  • 路由守卫:使用路由守卫控制权限
  • 动态路由:根据用户权限动态生成路由

69. 面试场景题 ​

69.1 实现一个响应式的计数器 ​

javascript
import { ref } from 'vue'

const count = ref(0)
const increment = () => count.value++
const decrement = () => count.value--

console.log(count.value) // 0
increment()
console.log(count.value) // 1

69.2 实现一个自定义 Hook ​

javascript
import { ref, onMounted, onUnmounted } from 'vue'

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)

  const updatePosition = (e) => {
    x.value = e.clientX
    y.value = e.clientY
  }

  onMounted(() => {
    window.addEventListener('mousemove', updatePosition)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', updatePosition)
  })

  return { x, y }
}

69.3 实现一个异步组件 ​

javascript
const AsyncComponent = defineAsyncComponent({
  loader: () => import('./AsyncComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000
})

70. Vue3 与其他框架的比较 ​

70.1 Vue3 与 React 的比较 ​

  • 响应式系统:Vue3 使用 Proxy,React 使用 useState/useReducer
  • 模板语法:Vue3 使用模板语法,React 使用 JSX
  • 性能:两者性能相当,但优化方式不同
  • 生态系统:React 生态更丰富,Vue 生态更简洁

70.2 Vue3 与 Angular 的比较 ​

  • 学习曲线:Vue3 学习曲线更平缓
  • 性能:Vue3 性能更好
  • 生态系统:Angular 生态更完善,但更复杂
  • 灵活性:Vue3 更灵活,Angular 更规范

71. Vue3 的最佳实践 ​

71.1 组件设计 ​

  • 单一职责:每个组件只负责一个功能
  • 可复用性:设计可复用的组件
  • 可维护性:组件代码清晰,易于维护

71.2 响应式数据 ​

  • 合理使用 ref 和 reactive:根据数据类型选择合适的响应式 API
  • 避免不必要的响应式转换:使用 markRaw 标记不需要响应式的对象
  • 合理使用计算属性:避免重复计算

71.3 性能优化 ​

  • 使用虚拟列表:处理大量数据的列表
  • 使用 KeepAlive:缓存组件实例
  • 使用异步组件:按需加载组件

72. 自定义指令的实现 ​

72.1 自定义指令的基本结构 ​

javascript
const vFocus = {
  mounted(el) {
    el.focus()
  }
}

app.directive('focus', vFocus)

72.2 自定义指令的生命周期 ​

  • beforeMount:指令绑定到元素但未挂载
  • mounted:元素已挂载
  • beforeUpdate:组件更新前
  • updated:组件更新后
  • beforeUnmount:元素卸载前
  • unmounted:元素已卸载

72.3 自定义指令的参数 ​

javascript
const vColor = {
  mounted(el, binding) {
    el.style.color = binding.value
  },
  updated(el, binding) {
    el.style.color = binding.value
  }
}

app.directive('color', vColor)

73. 动画与过渡效果 ​

73.1 基本过渡效果 ​

vue
<template>
  <transition name="fade">
    <div v-if="show">Hello World</div>
  </transition>
</template>

<style>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

73.2 列表过渡效果 ​

vue
<template>
  <transition-group name="list" tag="ul">
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </transition-group>
</template>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
</style>

73.3 使用第三方动画库 ​

vue
<template>
  <transition
    enter-active-class="animate__animated animate__fadeIn"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <div v-if="show">Hello World</div>
  </transition>
</template>

74. 表单处理 ​

74.1 基本表单处理 ​

vue
<template>
  <form @submit.prevent="handleSubmit">
    <input v-model="name" placeholder="Name" />
    <input v-model="email" placeholder="Email" />
    <button type="submit">Submit</button>
  </form>
</template>

<script setup>
import { ref } from 'vue'

const name = ref('')
const email = ref('')

const handleSubmit = () => {
  console.log({ name: name.value, email: email.value })
}
</script>

74.2 自定义表单验证 ​

vue
<template>
  <form @submit.prevent="handleSubmit">
    <input
      v-model="name"
      placeholder="Name"
      :class="{ 'is-invalid': errors.name }"
    />
    <div v-if="errors.name" class="error">{{ errors.name }}</div>
    
    <input
      v-model="email"
      placeholder="Email"
      :class="{ 'is-invalid': errors.email }"
    />
    <div v-if="errors.email" class="error">{{ errors.email }}</div>
    
    <button type="submit">Submit</button>
  </form>
</template>

<script setup>
import { ref, reactive } from 'vue'

const name = ref('')
const email = ref('')
const errors = reactive({})

const validate = () => {
  errors.name = name.value ? '' : 'Name is required'
  errors.email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value) ? '' : 'Invalid email'
  return !errors.name && !errors.email
}

const handleSubmit = () => {
  if (validate()) {
    console.log({ name: name.value, email: email.value })
  }
}
</script>

75. 测试 ​

75.1 单元测试 ​

javascript
import { mount } from '@vue/test-utils'
import Counter from './Counter.vue'

test('increments when button is clicked', () => {
  const wrapper = mount(Counter)
  const button = wrapper.find('button')
  
  button.trigger('click')
  expect(wrapper.text()).toContain('Count: 1')
})

75.2 组件测试 ​

javascript
import { mount } from '@vue/test-utils'
import HelloWorld from './HelloWorld.vue'

test('renders props.msg when passed', () => {
  const msg = 'new message'
  const wrapper = mount(HelloWorld, {
    props: { msg }
  })
  
  expect(wrapper.text()).toContain(msg)
})

76. 构建与部署 ​

76.1 Vite 构建配置 ​

javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  build: {
    outDir: 'dist',
    assetsDir: 'assets',
    sourcemap: false
  },
  server: {
    port: 3000,
    open: true
  }
})

76.2 环境变量配置 ​

javascript
// .env.development
VITE_API_URL=http://localhost:3000/api

// .env.production
VITE_API_URL=https://api.example.com
javascript
// 使用环境变量
const apiUrl = import.meta.env.VITE_API_URL

77. 国际化 (i18n) ​

77.1 基本配置 ​

javascript
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  locale: 'en',
  messages: {
    en: {
      hello: 'Hello'
    },
    zh: {
      hello: '你好'
    }
  }
})

app.use(i18n)

77.2 使用方式 ​

vue
<template>
  <div>{{ $t('hello') }}</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
console.log(t('hello'))
</script>

78. 移动端开发 ​

78.1 响应式布局 ​

vue
<template>
  <div class="container">
    <div class="content">Content</div>
    <div class="sidebar">Sidebar</div>
  </div>
</template>

<style>
.container {
  display: flex;
}

.content {
  flex: 1;
}

.sidebar {
  width: 200px;
}

@media (max-width: 768px) {
  .container {
    flex-direction: column;
  }
  
  .sidebar {
    width: 100%;
  }
}
</style>

78.2 触摸事件 ​

vue
<template>
  <div @touchstart="handleTouchStart" @touchmove="handleTouchMove"></div>
</template>

<script setup>
const handleTouchStart = (e) => {
  console.log('Touch start:', e.touches[0].clientX, e.touches[0].clientY)
}

const handleTouchMove = (e) => {
  console.log('Touch move:', e.touches[0].clientX, e.touches[0].clientY)
}
</script>

79. 性能优化实战 ​

79.1 虚拟列表实现 ​

vue
<template>
  <div class="virtual-list" ref="containerRef" @scroll="handleScroll">
    <div class="virtual-list__content" :style="{ height: totalHeight + 'px', paddingTop: offsetY + 'px' }">
      <div v-for="item in visibleItems" :key="item.id" class="virtual-list__item">
        {{ item.text }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'

const containerRef = ref(null)
const items = ref([])
const itemHeight = 50
const visibleCount = 20
const offset = ref(0)

// 生成测试数据
for (let i = 0; i < 10000; i++) {
  items.value.push({ id: i, text: `Item ${i}` })
}

const totalHeight = computed(() => items.value.length * itemHeight)
const offsetY = computed(() => offset.value * itemHeight)
const visibleItems = computed(() => {
  const start = Math.max(0, offset.value)
  const end = Math.min(items.value.length, start + visibleCount + 2)
  return items.value.slice(start, end)
})

const handleScroll = () => {
  if (containerRef.value) {
    offset.value = Math.floor(containerRef.value.scrollTop / itemHeight)
  }
}

onMounted(() => {
  if (containerRef.value) {
    containerRef.value.style.height = `${visibleCount * itemHeight}px`
  }
})
</script>

79.2 图片懒加载 ​

vue
<template>
  <img v-for="image in images" :key="image.id" :data-src="image.url" :ref="el => setImageRef(el, image)" class="lazy-image" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const images = ref([])
const imageRefs = ref(new Map())
let observer = null

// 生成测试数据
for (let i = 0; i < 100; i++) {
  images.value.push({
    id: i,
    url: `https://picsum.photos/id/${i}/200/300`
  })
}

const setImageRef = (el, image) => {
  if (el) {
    imageRefs.value.set(image.id, el)
  }
}

const loadImage = (el) => {
  const src = el.dataset.src
  if (!src) return
  
  el.src = src
  el.classList.remove('lazy-image')
  observer.unobserve(el)
}

onMounted(() => {
  observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadImage(entry.target)
      }
    })
  })
  
  imageRefs.value.forEach(el => {
    observer.observe(el)
  })
})

onUnmounted(() => {
  if (observer) {
    observer.disconnect()
  }
})
</script>

<style>
.lazy-image {
  width: 200px;
  height: 300px;
  background-color: #f0f0f0;
}
</style>

80. Vue3 与微前端 ​

80.1 微前端的基本概念 ​

  • 微前端:将大型前端应用拆分为多个独立的小型应用
  • 优点:独立开发、独立部署、技术栈无关

80.2 Vue3 与 Single-SPA ​

javascript
// 主应用配置
import { registerApplication, start } from 'single-spa'

registerApplication(
  'vue3-app',
  () => import('http://localhost:3001/vue3-app.js'),
  location => location.pathname.startsWith('/vue3-app')
)

start()
javascript
// Vue3 微应用配置
import { createApp } from 'vue'
import App from './App.vue'
import singleSpaVue from 'single-spa-vue'

const appOptions = {
  el: '#vue3-app',
  render: h => h(App)
}

const vueLifecycles = singleSpaVue({
  createApp, 
  appOptions
})

export const bootstrap = vueLifecycles.bootstrap
export const mount = vueLifecycles.mount
export const unmount = vueLifecycles.unmount

81. 总结 ​

Vue3 是 Vue.js 的新一代版本,带来了许多新特性和改进,包括:

  • 基于 Proxy 的响应式系统
  • 组合式 API
  • 编译优化
  • 更好的 TypeScript 支持
  • 更灵活的组件设计

掌握 Vue3 的核心概念和最佳实践,对于前端工程师来说非常重要。通过学习和实践,你可以更好地使用 Vue3 构建高性能、可维护的前端应用。

Released under the MIT License.