Vue3 面试题及详细答案120道(61-75 )
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 61. Vue3 中如何实现组件的异步加载?
- 62. 什么是 Vue3 的组合式 API 中的“依赖收集”?
- 63. Vue3 中如何使用 Teleport 组件?
- 64. Vue3 与 Vue2 的生命周期钩子有哪些区别?
- 65. Vue3 中 `v-model` 的实现原理是什么?
- 66. Vue3 中的 `ref` 和 `reactive` 有什么性能差异?
- 67. 如何在 Vue3 中实现全局状态管理(不使用 Vuex/Pinia)?
- 68. Vue3 中 `watch` 和 `watchEffect` 的区别是什么?
- 69. Vue3 中如何处理组件的错误边界?
- 70. 什么是 Vue3 的“渲染函数”?如何使用?
- 71. Vue3 中如何实现跨组件通信?
- 72. Vue3 中的 `defineProps` 和 `defineEmits` 有什么作用?
- 73. Vue3 中如何优化长列表渲染性能?
- 74. 什么是 Vue3 的“编译器宏”?有哪些常用宏?
- 75. Vue3 中如何实现路由守卫?
一、本文面试题目录
61. Vue3 中如何实现组件的异步加载?
在 Vue3 中,可通过 defineAsyncComponent
函数实现组件的异步加载,它能将组件加载推迟到需要时进行,提升初始加载速度。
- 基本用法:
import { defineAsyncComponent } from 'vue' // 异步加载组件 const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
- 高级配置(可指定加载状态、错误处理等):
const AsyncComponent = defineAsyncComponent({loader: () => import('./AsyncComponent.vue'),loadingComponent: LoadingComponent, // 加载中显示的组件errorComponent: ErrorComponent, // 加载失败显示的组件delay: 200, // 延迟多久后显示加载组件(默认 200ms)timeout: 3000 // 超时时间,超过则显示错误组件 })
62. 什么是 Vue3 的组合式 API 中的“依赖收集”?
依赖收集是 Vue3 响应式系统的核心机制,指追踪组件渲染过程中使用的响应式数据,当这些数据变化时,自动触发相关组件或副作用的重新执行。
- 实现原理:
- 执行副作用函数(如组件渲染函数、
watch
回调)时,Vue 会将当前副作用函数设为“活跃状态”。 - 访问响应式数据时,数据的
getter
会将活跃副作用函数添加到自身的依赖列表中。 - 当数据变化触发
setter
时,会遍历依赖列表,执行所有关联的副作用函数。
- 执行副作用函数(如组件渲染函数、
63. Vue3 中如何使用 Teleport 组件?
Teleport
( teleport 译为“传送”)用于将组件的 DOM 结构“传送”到页面的指定位置,解决嵌套组件样式或层级冲突问题(如模态框、弹窗)。
- 基本用法:
<template><button @click="showModal = true">打开弹窗</button><Teleport to="body"> <!-- 将内容传送到 body 标签下 --><div v-if="showModal" class="modal"><p>这是弹窗内容</p><button @click="showModal = false">关闭</button></div></Teleport> </template> <script setup> import { ref } from 'vue' const showModal = ref(false) </script>
- 注意:
to
属性值可以是 CSS 选择器或 DOM 元素,且传送的内容仍受当前组件的响应式数据控制。
64. Vue3 与 Vue2 的生命周期钩子有哪些区别?
Vue3 保留了大部分 Vue2 的生命周期概念,但在组合式 API 中采用函数式写法,且部分钩子名称有调整:
Vue2 选项式 API | Vue3 组合式 API | 说明 |
---|---|---|
beforeCreate | 无(可在 setup 中替代) | 初始化前,setup 执行时机类似 |
created | 无(可在 setup 中替代) | 初始化后,setup 执行时机类似 |
beforeMount | onBeforeMount | 挂载前触发 |
mounted | onMounted | 挂载后触发 |
beforeUpdate | onBeforeUpdate | 更新前触发 |
updated | onUpdated | 更新后触发 |
beforeDestroy | onBeforeUnmount | 卸载前触发(名称调整) |
destroyed | onUnmounted | 卸载后触发(名称调整) |
errorCaptured | onErrorCaptured | 捕获子组件错误时触发 |
- 新增钩子:
onRenderTracked
(渲染追踪时触发)、onRenderTriggered
(渲染触发时触发),用于调试响应式依赖。
65. Vue3 中 v-model
的实现原理是什么?
Vue3 中 v-model
是语法糖,本质是通过props 和事件实现父子组件数据双向绑定,相比 Vue2 更灵活,支持自定义修饰符和多个 v-model
。
- 基本原理:
- 父组件使用
v-model:propName="value"
时,等价于:propName="value" @update:propName="value = $event"
。 - 子组件通过
defineProps
接收propName
,并通过defineEmits
触发update:propName
事件传递新值。
- 父组件使用
- 示例:
<!-- 父组件 --> <ChildComponent v-model:count="num" /><!-- 子组件 --> <template><button @click="handleClick">点击</button> </template> <script setup> const props = defineProps(['count']) const emit = defineEmits(['update:count']) const handleClick = () => {emit('update:count', props.count + 1) // 触发更新事件 } </script>
66. Vue3 中的 ref
和 reactive
有什么性能差异?
ref
和 reactive
都是创建响应式数据的 API,但实现机制不同,性能表现有差异:
reactive
:- 基于 Proxy 实现,直接代理对象,对对象的属性访问/修改进行拦截。
- 性能:对于大型对象,初始化时需要递归代理所有属性,可能有轻微性能损耗;但访问属性时无需额外解包,效率较高。
ref
:- 用于基本类型或单个值,内部通过
{ value: ... }
包装,访问/修改需通过.value
。 - 性能:初始化轻量,但每次访问/修改都需经过
.value
的 getter/setter,频繁操作时可能比reactive
略慢。
- 用于基本类型或单个值,内部通过
- 建议:
- 基本类型或单个值用
ref
,对象/数组用reactive
。 - 频繁操作的响应式数据优先考虑
reactive
(如复杂表单)。
- 基本类型或单个值用
67. 如何在 Vue3 中实现全局状态管理(不使用 Vuex/Pinia)?
可通过响应式数据 + 全局注入实现简单全局状态管理,适用于小型项目:
- 创建全局响应式数据:
// store.js import { reactive, readonly } from 'vue' const state = reactive({userInfo: null,theme: 'light' }) // 对外暴露只读版本,防止直接修改 const globalState = readonly(state) // 定义修改状态的方法 const mutations = {setUserInfo(info) {state.userInfo = info},toggleTheme() {state.theme = state.theme === 'light' ? 'dark' : 'light'} } export { globalState, mutations }
- 在组件中使用:
<script setup> import { globalState, mutations } from './store.js' // 读取状态 console.log(globalState.theme) // 修改状态 mutations.setUserInfo({ name: 'Vue3' }) </script>
- 缺点:缺乏 Vuex/Pinia 的模块化、DevTools 追踪、中间件等功能,大型项目仍建议使用 Pinia。
68. Vue3 中 watch
和 watchEffect
的区别是什么?
watch
和 watchEffect
都是监听响应式数据变化的 API,但用法和场景不同:
特性 | watch | watchEffect |
---|---|---|
依赖指定 | 需明确指定监听的数据源(如 watch(refA, ...) ) | 自动收集依赖(函数内使用的响应式数据均会被监听) |
初始执行 | 默认不执行,需通过 immediate: true 开启 | 默认初始执行一次 |
回调参数 | 可获取新旧值((newVal, oldVal) => {} ) | 无参数,仅执行副作用 |
适用场景 | 需明确监听特定数据,或需要新旧值对比 | 依赖不明确,或需要初始执行的副作用(如数据加载) |
- 示例:
const count = ref(0) // watch:明确监听 count watch(count, (newVal, oldVal) => {console.log('count变化:', newVal, oldVal) })// watchEffect:自动监听内部使用的 count watchEffect(() => {console.log('count当前值:', count.value) })
69. Vue3 中如何处理组件的错误边界?
Vue3 中可通过 onErrorCaptured
钩子或自定义错误边界组件捕获子组件的错误,防止错误扩散导致整个应用崩溃:
- 方法1:使用
onErrorCaptured
钩子(组件内局部捕获):<script setup> import { onErrorCaptured, ref } from 'vue' const hasError = ref(false)onErrorCaptured((err, instance, info) => {console.error('捕获到错误:', err, info)hasError.value = truereturn true // 阻止错误继续向上传播 }) </script>
- 方法2:自定义错误边界组件(全局复用):
<!-- ErrorBoundary.vue --> <template><slot v-if="!hasError" /><div v-else>发生错误:{{ error.message }}</div> </template> <script setup> import { ref, onErrorCaptured } from 'vue' const hasError = ref(false) const error = ref(null)onErrorCaptured((err) => {hasError.value = trueerror.value = errreturn true }) </script>
- 使用错误边界组件:
<ErrorBoundary><ChildComponent /> <!-- 子组件的错误会被捕获 --> </ErrorBoundary>
70. 什么是 Vue3 的“渲染函数”?如何使用?
渲染函数是用 JavaScript 代码描述组件渲染内容的函数,相比模板更灵活,适用于动态生成复杂 DOM 结构的场景。Vue3 中通过 h
函数(createVNode
的别名)创建虚拟 DOM 节点。
- 基本用法:
<script setup> import { h } from 'vue'// 定义渲染函数组件 const MyComponent = (props) => {return h('div', { class: 'my-component' }, [h('h1', `Hello ${props.name}`),h('p', '这是渲染函数生成的内容')]) }MyComponent.props = ['name'] // 声明 props </script><template><MyComponent name="Vue3" /> </template>
- 与模板的关系:模板最终会被编译为渲染函数,渲染函数是 Vue 内部渲染的底层实现。
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
71. Vue3 中如何实现跨组件通信?
Vue3 提供多种跨组件通信方式,适用于不同场景:
- Props / Emits:父子组件通信,父传子用
props
,子传父用emits
。 - Provide / Inject:祖孙组件通信,祖先通过
provide
提供数据,后代通过inject
接收。// 祖先组件 import { provide } from 'vue' provide('theme', 'dark')// 后代组件 import { inject } from 'vue' const theme = inject('theme', 'light') // 第二个参数为默认值
- 全局状态管理:如 Pinia、Vuex,适用于任意组件间共享状态。
- 事件总线(Event Bus):通过
mitt
库实现(Vue3 移除了内置$on/$emit
):// bus.js import mitt from 'mitt' export const bus = mitt()// 组件A发送事件 bus.emit('user-updated', userInfo)// 组件B接收事件 bus.on('user-updated', (info) => { ... })
72. Vue3 中的 defineProps
和 defineEmits
有什么作用?
defineProps
和 defineEmits
是 Vue3 <script setup>
语法中用于声明组件 props 和自定义事件的编译宏,无需导入即可使用:
defineProps
:声明组件接收的 props,支持类型校验和默认值。// 基础用法 const props = defineProps(['name', 'age'])// 带类型和默认值 const props = defineProps({name: { type: String, required: true },age: { type: Number, default: 18 } })
defineEmits
:声明组件可触发的自定义事件,支持类型校验。// 基础用法 const emit = defineEmits(['change', 'submit'])// 带类型校验 const emit = defineEmits({change: (value) => typeof value === 'string', // 验证事件参数submit: () => true // 无需参数 })// 触发事件 emit('change', 'new value')
- 特点:在
<script setup>
中自动具备类型推断,且返回的props
是响应式的(只读),emit
是触发事件的函数。
73. Vue3 中如何优化长列表渲染性能?
长列表(如 1000+ 条数据)渲染可能导致性能问题,可通过以下方式优化:
- 虚拟滚动:只渲染可视区域内的列表项,通过
vue-virtual-scroller
等库实现。<template><RecycleScroller:items="longList":item-size="50"class="scroller"><template v-slot="{ item }"><div class="list-item">{{ item.text }}</div></template></RecycleScroller> </template>
- 懒加载:监听滚动事件,当列表滚动到一定位置时再加载更多数据。
- 避免响应式数据冗余:长列表数据若无需响应式,可使用
markRaw
标记为非响应式。import { markRaw } from 'vue' const longList = markRaw([/* 大量数据 */]) // 关闭响应式
- 使用
v-memo
:缓存列表项,仅当依赖变化时重新渲染。<div v-for="item in list" :key="item.id" v-memo="[item.id, item.name]">{{ item.name }} </div>
74. 什么是 Vue3 的“编译器宏”?有哪些常用宏?
编译器宏是 Vue3 中仅在 <script setup>
或 setup()
函数中生效的特殊函数,由 Vue 编译器处理,无需导入即可使用,用于简化组件逻辑:
- 常用编译器宏:
defineProps
:声明组件 props。defineEmits
:声明组件自定义事件。defineExpose
:暴露组件内部属性/方法,供父组件通过ref
访问。const count = ref(0) const increment = () => count.value++ defineExpose({ count, increment }) // 父组件可通过 ref 访问
withDefaults
:为defineProps
提供更灵活的默认值配置(支持函数返回默认值)。const props = withDefaults(defineProps({list: { type: Array },config: { type: Object } }), {list: () => [], // 函数返回默认值(避免复用同一引用)config: () => ({ theme: 'light' }) })
75. Vue3 中如何实现路由守卫?
Vue3 中路由守卫的用法与 Vue2 类似,但需结合 Vue Router 4+ 的 API,常用守卫包括:
- 全局守卫:
// router/index.js import { createRouter } from 'vue-router' const router = createRouter(/* 配置 */)// 全局前置守卫(跳转前触发) router.beforeEach((to, from, next) => {if (to.meta.requiresAuth && !isLogin) {next('/login') // 未登录则跳转到登录页} else {next() // 允许跳转} })// 全局后置守卫(跳转后触发) router.afterEach((to, from) => {document.title = to.meta.title || '默认标题' })
- 路由独享守卫(在路由配置中定义):
const routes = [{path: '/admin',component: Admin,beforeEnter: (to, from, next) => { // 仅当前路由生效if (!isAdmin) next('/forbidden')else next()}} ]
- 组件内守卫:
<script setup> import { onBeforeRouteEnter, onBeforeRouteLeave } from 'vue-router'// 进入组件前触发(此时组件实例未创建,无法访问 this) onBeforeRouteEnter((to, from, next) => {next(vm => { // 通过 vm 访问组件实例vm.fetchData()}) })// 离开组件前触发 onBeforeRouteLeave((to, from, next) => {if (confirm('确定离开吗?')) next()else next(false) // 取消跳转 }) </script>