Vue 3 面试题 - 基础概念与核心特性

目录

  1. [Vue 3 新特性](#Vue 3 新特性)
  2. 响应式系统
  3. [组合式 API (Composition API)](#组合式 API (Composition API))
  4. 生命周期
  5. 模板语法与指令

Vue 3 新特性

Q1: Vue 3 相比 Vue 2 有哪些重大改进?

详细解答:

Vue 3 带来了多个重大改进,主要包括:

1. 性能优化

  • 虚拟 DOM 重写:优化了 diff 算法,性能提升 1.3-2 倍
  • 静态提升:将静态节点提升到渲染函数外部,避免重复创建
  • 事件监听缓存:cacheHandlers 优化事件处理
  • SSR 性能提升:服务端渲染速度提升 2-3 倍

2. Composition API

  • 更好的逻辑复用和代码组织
  • TypeScript 支持更友好
  • 解决了 Options API 的局限性

3. 更好的 TypeScript 支持

  • 完全使用 TypeScript 重写
  • 类型推导更准确
  • IDE 支持更完善

4. 新增特性

  • Fragment:支持多根节点
  • Teleport:可以将组件渲染到 DOM 的其他位置
  • Suspense:异步组件的加载状态处理
  • Multiple v-model:支持多个 v-model 绑定

5. Tree-shaking 支持

  • 按需引入,减小打包体积
  • 未使用的功能可以被移除

代码示例:

javascript 复制代码
// Vue 2 - Options API
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('Component mounted')
  }
}

// Vue 3 - Composition API
import { ref, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    const increment = () => {
      count.value++
    }
    
    onMounted(() => {
      console.log('Component mounted')
    })
    
    return {
      count,
      increment
    }
  }
}

响应式系统

Q2: Vue 3 的响应式原理是什么?与 Vue 2 有什么区别?

详细解答:

Vue 3 响应式原理:

Vue 3 使用 Proxy 替代了 Vue 2 的 Object.defineProperty,实现了更强大的响应式系统。

核心机制:

  1. Proxy 拦截:拦截对象的读取、设置、删除等操作
  2. effect 函数:副作用函数,当依赖的响应式数据变化时自动执行
  3. track 函数:依赖收集,记录哪些 effect 依赖了哪些属性
  4. trigger 函数:派发更新,当属性变化时触发相关的 effect

与 Vue 2 的区别:

特性 Vue 2 (Object.defineProperty) Vue 3 (Proxy)
新增属性 需要 Vue.set() 自动响应
删除属性 需要 Vue.delete() 自动响应
数组索引 无法直接监听 可以直接监听
数组长度 无法监听 可以监听
Map/Set 不支持 支持
性能 初始化时遍历所有属性 惰性处理,访问时才代理

代码实现示例:

javascript 复制代码
// Vue 3 响应式核心简化实现
let activeEffect = null

function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      // 依赖收集
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      // 触发更新
      trigger(target, key)
      return result
    }
  }
  
  return new Proxy(target, handler)
}

const targetMap = new WeakMap()

function track(target, key) {
  if (!activeEffect) return
  
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

// 使用示例
const state = reactive({ count: 0 })

effect(() => {
  console.log('count is:', state.count)
})

state.count++ // 输出: count is: 1

优势总结:

  1. 更完整的拦截:可以拦截更多操作(in、delete、has 等)
  2. 更好的性能:不需要遍历所有属性进行劫持
  3. 更好的数组处理:直接监听数组变化
  4. 支持更多数据结构:Map、Set、WeakMap、WeakSet

组合式 API

Q3: 什么是 Composition API?相比 Options API 有什么优势?

详细解答:

Composition API 定义:

Composition API 是 Vue 3 引入的一套新的 API,通过 setup() 函数组织组件逻辑,提供了更灵活的代码组织方式。

核心概念:

  1. setup() 函数:组件的入口点,在组件创建之前执行
  2. 响应式 API:ref、reactive、computed、watch 等
  3. 生命周期钩子:onMounted、onUpdated 等
  4. 依赖注入:provide、inject

相比 Options API 的优势:

1. 更好的逻辑复用

javascript 复制代码
// Options API - 逻辑分散
export default {
  data() {
    return {
      // 用户相关
      user: null,
      // 产品相关
      products: []
    }
  },
  methods: {
    // 用户相关
    fetchUser() {},
    // 产品相关
    fetchProducts() {}
  },
  mounted() {
    // 用户相关
    this.fetchUser()
    // 产品相关
    this.fetchProducts()
  }
}

// Composition API - 逻辑聚合
import { useUser } from './composables/useUser'
import { useProducts } from './composables/useProducts'

export default {
  setup() {
    // 用户相关逻辑完全聚合
    const { user, fetchUser } = useUser()
    
    // 产品相关逻辑完全聚合
    const { products, fetchProducts } = useProducts()
    
    return {
      user,
      fetchUser,
      products,
      fetchProducts
    }
  }
}

2. 可组合函数 (Composables)

javascript 复制代码
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)
  
  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  
  return { x, y }
}

// 在组件中使用
import { useMouse } from './composables/useMouse'

export default {
  setup() {
    const { x, y } = useMouse()
    return { x, y }
  }
}

3. 更好的 TypeScript 支持

typescript 复制代码
import { ref, Ref } from 'vue'

interface User {
  id: number
  name: string
  email: string
}

export function useUser() {
  const user: Ref<User | null> = ref(null)
  const loading: Ref<boolean> = ref(false)
  
  async function fetchUser(id: number): Promise<void> {
    loading.value = true
    try {
      const response = await fetch(`/api/users/${id}`)
      user.value = await response.json()
    } finally {
      loading.value = false
    }
  }
  
  return {
    user,
    loading,
    fetchUser
  }
}

4. 更灵活的代码组织

javascript 复制代码
// Options API - 按选项类型组织
export default {
  data() {
    return {
      searchQuery: '',
      searchResults: [],
      isSearching: false,
      sortBy: 'date',
      filterType: 'all'
    }
  },
  computed: {
    filteredResults() {
      // 复杂的过滤逻辑
    },
    sortedResults() {
      // 复杂的排序逻辑
    }
  },
  methods: {
    async search() {},
    updateFilter() {},
    updateSort() {}
  }
}

// Composition API - 按功能组织
import { ref, computed } from 'vue'

export default {
  setup() {
    // 搜索功能
    const searchQuery = ref('')
    const searchResults = ref([])
    const isSearching = ref(false)
    
    async function search() {
      // 搜索逻辑
    }
    
    // 过滤功能
    const filterType = ref('all')
    const filteredResults = computed(() => {
      // 过滤逻辑
    })
    
    function updateFilter(type) {
      filterType.value = type
    }
    
    // 排序功能
    const sortBy = ref('date')
    const sortedResults = computed(() => {
      // 排序逻辑
    })
    
    function updateSort(field) {
      sortBy.value = field
    }
    
    return {
      searchQuery,
      searchResults,
      isSearching,
      search,
      filterType,
      filteredResults,
      updateFilter,
      sortBy,
      sortedResults,
      updateSort
    }
  }
}

5. 避免 this 指向问题

javascript 复制代码
// Options API - this 指向可能混乱
export default {
  methods: {
    fetchData() {
      setTimeout(function() {
        // this 指向错误
        this.data = []
      }, 1000)
      
      setTimeout(() => {
        // 需要使用箭头函数
        this.data = []
      }, 1000)
    }
  }
}

// Composition API - 没有 this
import { ref } from 'vue'

export default {
  setup() {
    const data = ref([])
    
    function fetchData() {
      setTimeout(() => {
        // 直接使用,无需担心 this
        data.value = []
      }, 1000)
    }
    
    return { data, fetchData }
  }
}

总结:

Composition API 的核心优势是:

  • 逻辑聚合:相关逻辑可以组织在一起
  • 逻辑复用:通过 composables 轻松复用
  • 类型推导:TypeScript 支持更好
  • 代码组织:更灵活的组织方式
  • tree-shaking:未使用的功能可以被移除

生命周期

Q4: Vue 3 的生命周期钩子有哪些?与 Vue 2 有什么区别?

详细解答:

Vue 3 Composition API 生命周期:

Options API Composition API 执行时机
beforeCreate setup() 组件实例创建之前
created setup() 组件实例创建之后
beforeMount onBeforeMount 挂载开始之前
mounted onMounted 挂载完成后
beforeUpdate onBeforeUpdate 数据更新,DOM 更新前
updated onUpdated DOM 更新后
beforeUnmount onBeforeUnmount 卸载之前
unmounted onUnmounted 卸载完成后
errorCaptured onErrorCaptured 捕获子组件错误
renderTracked onRenderTracked 调试钩子,跟踪依赖
renderTriggered onRenderTriggered 调试钩子,触发重新渲染
activated onActivated keep-alive 组件激活
deactivated onDeactivated keep-alive 组件停用

主要变化:

  1. beforeDestroybeforeUnmount
  2. destroyedunmounted
  3. setup() 替代了 beforeCreate 和 created

完整示例:

javascript 复制代码
import {
  ref,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered
} from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    console.log('setup - 相当于 beforeCreate 和 created')
    
    onBeforeMount(() => {
      console.log('onBeforeMount - DOM 挂载前')
    })
    
    onMounted(() => {
      console.log('onMounted - DOM 挂载完成')
      // 适合进行 DOM 操作、发送 API 请求
    })
    
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate - 数据更新,DOM 更新前')
    })
    
    onUpdated(() => {
      console.log('onUpdated - DOM 更新完成')
      // 注意:避免在这里修改状态,可能导致无限循环
    })
    
    onBeforeUnmount(() => {
      console.log('onBeforeUnmount - 组件卸载前')
      // 清理定时器、事件监听等
    })
    
    onUnmounted(() => {
      console.log('onUnmounted - 组件已卸载')
    })
    
    // keep-alive 专用
    onActivated(() => {
      console.log('onActivated - 组件被激活')
    })
    
    onDeactivated(() => {
      console.log('onDeactivated - 组件被停用')
    })
    
    // 错误捕获
    onErrorCaptured((err, instance, info) => {
      console.error('捕获到错误:', err, info)
      return false // 返回 false 阻止错误继续传播
    })
    
    // 调试钩子
    onRenderTracked((event) => {
      console.log('依赖被跟踪:', event)
    })
    
    onRenderTriggered((event) => {
      console.log('触发重新渲染:', event)
    })
    
    return {
      count
    }
  }
}

实际应用场景:

javascript 复制代码
import { ref, onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    const data = ref(null)
    let timer = null
    
    // 组件挂载后执行
    onMounted(async () => {
      // 1. 发送 API 请求
      const response = await fetch('/api/data')
      data.value = await response.json()
      
      // 2. 添加事件监听
      window.addEventListener('resize', handleResize)
      
      // 3. 启动定时器
      timer = setInterval(() => {
        console.log('定时任务')
      }, 1000)
      
      // 4. 初始化第三方库
      initThirdPartyLibrary()
    })
    
    // 组件卸载前清理
    onBeforeUnmount(() => {
      // 1. 移除事件监听
      window.removeEventListener('resize', handleResize)
      
      // 2. 清除定时器
      if (timer) {
        clearInterval(timer)
      }
      
      // 3. 销毁第三方库实例
      destroyThirdPartyLibrary()
    })
    
    function handleResize() {
      console.log('窗口大小改变')
    }
    
    return {
      data
    }
  }
}

注意事项:

  1. setup() 中不能使用 this:因为 setup 执行时组件实例还未创建
  2. 生命周期钩子可以多次调用:同一个钩子可以注册多次
  3. onUpdated 避免修改状态:可能导致无限更新循环
  4. 清理副作用:在 onBeforeUnmount 中清理定时器、事件监听等

模板语法与指令

Q5: Vue 3 中的 Fragment、Teleport 和 Suspense 是什么?

详细解答:

1. Fragment (片段)

定义: Vue 3 支持多根节点,不再要求模板必须有单一根元素。

Vue 2 vs Vue 3:

vue 复制代码
<!-- Vue 2 - 必须有单一根元素 -->
<template>
  <div>
    <header>Header</header>
    <main>Content</main>
    <footer>Footer</footer>
  </div>
</template>

<!-- Vue 3 - 支持多根节点 -->
<template>
  <header>Header</header>
  <main>Content</main>
  <footer>Footer</footer>
</template>

优势:

  • 减少不必要的 DOM 嵌套
  • 更清晰的组件结构
  • 避免样式污染

2. Teleport (传送门)

定义: 将组件的 HTML 渲染到 DOM 的其他位置,而不影响组件的逻辑关系。

典型场景: Modal 弹窗、Toast 提示、Dropdown 下拉菜单

基本用法:

vue 复制代码
<template>
  <div class="container">
    <button @click="showModal = true">打开弹窗</button>
    
    <!-- 将 modal 渲染到 body 下 -->
    <Teleport to="body">
      <div v-if="showModal" class="modal">
        <div class="modal-content">
          <h2>这是一个弹窗</h2>
          <button @click="showModal = false">关闭</button>
        </div>
      </div>
    </Teleport>
  </div>
</template>

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

const showModal = ref(false)
</script>

<style scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-content {
  background: white;
  padding: 20px;
  border-radius: 8px;
}
</style>

高级用法 - 条件渲染目标:

vue 复制代码
<template>
  <Teleport :to="isMobile ? '#mobile-menu' : '#desktop-menu'" :disabled="!shouldTeleport">
    <nav>
      <a href="/">首页</a>
      <a href="/about">关于</a>
    </nav>
  </Teleport>
</template>

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

const isMobile = ref(window.innerWidth < 768)
const shouldTeleport = ref(true)
</script>

多个 Teleport 到同一目标:

vue 复制代码
<!-- 组件 A -->
<Teleport to="#modals">
  <div>Modal A</div>
</Teleport>

<!-- 组件 B -->
<Teleport to="#modals">
  <div>Modal B</div>
</Teleport>

<!-- 渲染结果 -->
<div id="modals">
  <div>Modal A</div>
  <div>Modal B</div>
</div>

3. Suspense (悬念)

定义: 处理异步组件的加载状态,提供统一的加载和错误处理机制。

基本用法:

vue 复制代码
<template>
  <Suspense>
    <!-- 异步组件 -->
    <template #default>
      <AsyncComponent />
    </template>
    
    <!-- 加载中状态 -->
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

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

const AsyncComponent = defineAsyncComponent(() =>
  import('./AsyncComponent.vue')
)
</script>

异步组件示例:

vue 复制代码
<!-- AsyncComponent.vue -->
<template>
  <div>
    <h1>{{ user.name }}</h1>
    <p>{{ user.email }}</p>
  </div>
</template>

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

// 在 setup 中使用 await
const user = ref(null)

// 模拟异步数据获取
const response = await fetch('/api/user')
user.value = await response.json()
</script>

嵌套 Suspense:

vue 复制代码
<template>
  <Suspense>
    <template #default>
      <div>
        <UserProfile />
        
        <!-- 嵌套的 Suspense -->
        <Suspense>
          <template #default>
            <UserPosts />
          </template>
          <template #fallback>
            <div>加载文章中...</div>
          </template>
        </Suspense>
      </div>
    </template>
    
    <template #fallback>
      <div>加载用户信息中...</div>
    </template>
  </Suspense>
</template>

错误处理:

vue 复制代码
<template>
  <Suspense @pending="onPending" @resolve="onResolve" @fallback="onFallback">
    <template #default>
      <AsyncComponent />
    </template>
    
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
  
  <!-- 使用 ErrorBoundary 捕获错误 -->
  <div v-if="error" class="error">
    {{ error.message }}
  </div>
</template>

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

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  return false
})

function onPending() {
  console.log('开始加载')
}

function onResolve() {
  console.log('加载完成')
}

function onFallback() {
  console.log('显示 fallback')
}
</script>

实际应用 - 完整示例:

vue 复制代码
<!-- App.vue -->
<template>
  <div id="app">
    <Suspense>
      <template #default>
        <Dashboard />
      </template>
      
      <template #fallback>
        <div class="loading-container">
          <div class="spinner"></div>
          <p>加载数据中...</p>
        </div>
      </template>
    </Suspense>
    
    <!-- Teleport 示例 -->
    <Teleport to="body">
      <NotificationContainer />
    </Teleport>
  </div>
</template>

<!-- Dashboard.vue -->
<script setup>
import { ref } from 'vue'

// 并行获取多个异步数据
const [userData, statsData, postsData] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/stats').then(r => r.json()),
  fetch('/api/posts').then(r => r.json())
])

const user = ref(userData)
const stats = ref(statsData)
const posts = ref(postsData)
</script>

<template>
  <div class="dashboard">
    <h1>{{ user.name }}</h1>
    <Stats :data="stats" />
    <PostList :posts="posts" />
  </div>
</template>

总结:

特性 用途 主要场景
Fragment 多根节点 减少不必要的包装元素
Teleport DOM 位置传送 Modal、Toast、Dropdown
Suspense 异步组件加载 数据获取、代码分割、懒加载
相关推荐
2501_944525545 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
打小就很皮...5 小时前
《在 React/Vue 项目中引入 Supademo 实现交互式新手指引》
前端·supademo·新手指引
C澒5 小时前
系统初始化成功率下降排查实践
前端·安全·运维开发
摘星编程6 小时前
React Native + OpenHarmony:自定义useFormik表单处理
javascript·react native·react.js
C澒6 小时前
面单打印服务的监控检查事项
前端·后端·安全·运维开发·交通物流
pas1366 小时前
39-mini-vue 实现解析 text 功能
前端·javascript·vue.js
qq_532453536 小时前
使用 GaussianSplats3D 在 Vue 3 中构建交互式 3D 高斯点云查看器
前端·vue.js·3d
Swift社区6 小时前
Flutter 路由系统,对比 RN / Web / iOS 有什么本质不同?
前端·flutter·ios
2601_949833397 小时前
flutter_for_openharmony口腔护理app实战+我的实现
开发语言·javascript·flutter
雾眠气泡水@7 小时前
前端:解决同一张图片由于页面大小不统一导致图片模糊
前端