Vue3 面试题及详细答案120道(91-105 )
《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 91. Vue3 中如何使用 `v-once` 优化性能?
- 92. 请解释 Vue3 中 `toRef` 与 `toRefs` 的区别及使用场景?
- 93. Vue3 中如何实现组件的动态切换?
- 94. 请描述 Vue3 中 `v-for` 与 `v-if` 同时使用的注意事项?
- 95. Vue3 中如何实现自定义的表单控件(如封装 Input 组件)?
- 96. 请解释 Vue3 中 `runtime-core` 与 `runtime-dom` 的区别?
- 97. Vue3 中如何使用 `provide`/`inject` 实现跨层级组件通信并保持响应式?
- 98. 请描述 Vue3 中模板编译的优化策略?
- 99. Vue3 中如何使用 `Suspense` 结合异步组件和异步数据加载?
- 100. 请解释 Vue3 中 `ref` 的实现原理?
- 101. Vue3 中如何实现组件的懒加载及代码分割?
- 102. 请对比 Vue3 与 Vue2 的虚拟 DOM 差异?
- 103. Vue3 中如何使用 `v-slot` 定义和使用插槽?
- 104. 请描述 Vue3 中 `reactive` 的实现原理?
- 105. Vue3 中如何实现基于路由的权限控制?
- 二、120道面试题目录列表
一、本文面试题目录
91. Vue3 中如何使用 v-once
优化性能?
v-once
是 Vue 的内置指令,用于标记元素或组件只渲染一次,后续即使数据变化也不会重新渲染,从而优化性能。
使用场景:
- 内容固定不变的元素(如静态文本、版权信息、固定的标题等)。
- 渲染代价较高但数据不会变化的组件。
示例:
<!-- 静态文本,只渲染一次 -->
<p v-once>© 2023 Vue3 面试题集(内容固定)</p><!-- 数据初始渲染后不变,使用 v-once 避免重复渲染 -->
<div v-once><h2>{{ initialTitle }}</h2><p>{{ initialDescription }}</p>
</div>
注意:v-once
会阻止其子元素的响应式更新,因此仅适用于确知内容不会变化的场景,滥用可能导致数据更新后视图不刷新的问题。
92. 请解释 Vue3 中 toRef
与 toRefs
的区别及使用场景?
toRef
和 toRefs
都是用于将响应式对象的属性转为 ref
对象的 API,以保持响应式特性,但适用场景不同:
toRef
:
- 功能:将响应式对象的单个属性转为
ref
对象,与原对象保持关联。 - 场景:仅需使用响应式对象的某个属性,且希望该属性保持响应式。
- 示例:
import { reactive, toRef } from 'vue'; const obj = reactive({ name: 'Vue3', age: 3 }); const nameRef = toRef(obj, 'name'); // 仅转换 name 属性 nameRef.value = 'Vue'; // 修改会同步到原对象,触发更新
toRefs
:
- 功能:将响应式对象的所有属性转为
ref
对象,返回一个包含这些ref
的普通对象。 - 场景:需要解构响应式对象的多个属性,且希望所有属性保持响应式。
- 示例:
import { reactive, toRefs } from 'vue'; const obj = reactive({ name: 'Vue3', age: 3 }); const { name, age } = toRefs(obj); // 所有属性转为 ref name.value = 'Vue'; // 同步更新原对象 age.value = 4; // 同步更新原对象
总结:toRef
用于单个属性,toRefs
用于多个属性,均用于解决响应式对象解构后丢失响应式的问题。
93. Vue3 中如何实现组件的动态切换?
组件的动态切换指根据条件或状态渲染不同的组件,Vue3 中可通过以下方式实现:
-
使用
<component>
标签配合:is
属性:<template><!-- :is 绑定组件名称或组件对象 --><component :is="currentComponent"></component><button @click="currentComponent = ComponentA">显示A</button><button @click="currentComponent = ComponentB">显示B</button> </template> <script setup> import { ref } from 'vue'; import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; const currentComponent = ref(ComponentA); // 初始显示 ComponentA </script>
-
通过
v-if
/v-else-if
/v-else
条件渲染:<template><ComponentA v-if="type === 'A'"></ComponentA><ComponentB v-else-if="type === 'B'"></ComponentB><ComponentC v-else></ComponentC><button @click="type = 'A'">切换到A</button><button @click="type = 'B'">切换到B</button> </template> <script setup> import { ref } from 'vue'; import ComponentA from './ComponentA.vue'; import ComponentB from './ComponentB.vue'; import ComponentC from './ComponentC.vue'; const type = ref('A'); </script>
对比:
<component :is>
适用于组件类型较多、切换频繁的场景,更灵活。v-if
系列适用于条件简单、组件类型较少的场景,可读性更强。
94. 请描述 Vue3 中 v-for
与 v-if
同时使用的注意事项?
在 Vue 中,v-for
和 v-if
同时使用可能导致逻辑混淆和性能问题,需注意以下事项:
-
优先级问题:
- Vue3 中
v-for
的优先级高于v-if
,即先循环渲染所有项,再对每个项执行v-if
判断。 - 示例:
<!-- 先循环所有 item,再判断是否显示 --> <li v-for="item in list" v-if="item.active" :key="item.id">{{ item.name }} </li>
- 问题:即使
item.active
为false
,v-for
仍会循环该 item,造成不必要的渲染开销。
- Vue3 中
-
优化方案:
- 使用计算属性过滤数据:先过滤出需要渲染的项,再循环,减少循环次数。
import { ref, computed } from 'vue'; const list = ref([/* 原始数据 */]); const filteredList = computed(() => list.value.filter(item => item.active));
<li v-for="item in filteredList" :key="item.id">{{ item.name }} </li>
- 在外层包裹元素使用
v-if
:若需整体条件渲染(如列表是否显示),将v-if
放在v-for
外层。<ul v-if="showList"> <!-- 先判断是否显示列表,再循环 --><li v-for="item in list" :key="item.id">{{ item.name }}</li> </ul>
- 使用计算属性过滤数据:先过滤出需要渲染的项,再循环,减少循环次数。
-
避免逻辑混淆:同时使用时需明确目的(是过滤项还是控制整体显示),优先通过计算属性优化。
95. Vue3 中如何实现自定义的表单控件(如封装 Input 组件)?
封装自定义表单控件需支持 v-model
双向绑定,使组件能像原生表单元素一样使用。Vue3 中实现方式如下:
示例:封装一个带校验的 Input 组件
<!-- CustomInput.vue -->
<template><div class="custom-input"><input:value="modelValue"@input="$emit('update:modelValue', $event.target.value)"@blur="handleBlur":class="{ 'error': hasError }"/><p class="error-message" v-if="hasError">{{ errorMessage }}</p></div>
</template><script setup>
import { defineProps, defineEmits, computed } from 'vue';// 接收 v-model 绑定的值(默认用 modelValue)
const props = defineProps({modelValue: {type: String,default: ''},// 校验规则(可选)validator: {type: Function,default: () => true}
});// 声明可触发的事件
const emit = defineEmits(['update:modelValue', 'blur']);// 校验逻辑
const hasError = computed(() => !props.validator(props.modelValue));
const errorMessage = computed(() => `请输入有效的值(当前值:${props.modelValue})`);// 处理失焦事件
const handleBlur = (e) => {emit('blur', e); // 向外触发 blur 事件
};
</script>
使用自定义 Input 组件:
<template><CustomInputv-model="username":validator="validateUsername"@blur="handleInputBlur"/>
</template><script setup>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';const username = ref('');// 校验规则:用户名长度 >= 3
const validateUsername = (value) => value.length >= 3;// 处理失焦
const handleInputBlur = (e) => {console.log('输入框失焦', e.target.value);
};
</script>
核心原理:通过 modelValue
接收值,update:modelValue
事件更新值,实现 v-model
双向绑定,同时支持自定义事件和校验逻辑。
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
96. 请解释 Vue3 中 runtime-core
与 runtime-dom
的区别?
Vue3 的核心包被拆分为多个模块,@vue/runtime-core
和 @vue/runtime-dom
是其中两个核心模块,职责不同:
@vue/runtime-core
:
- 功能:Vue3 的核心运行时,包含虚拟 DOM、组件系统、响应式核心、生命周期等与平台无关的核心逻辑。
- 平台无关性:不依赖浏览器 DOM API,可用于非浏览器环境(如 Node.js 服务端渲染、小程序等)。
- 主要内容:
- 虚拟 DOM 的创建、渲染、更新逻辑。
- 组件实例的创建和管理。
- 响应式系统的核心实现(依赖收集、触发更新)。
@vue/runtime-dom
:
- 功能:基于
runtime-core
,针对浏览器环境的运行时扩展,提供 DOM 相关的实现。 - 平台相关性:依赖浏览器 DOM API,负责将虚拟 DOM 转换为真实 DOM。
- 主要内容:
- DOM 元素的创建、插入、删除等操作。
- 事件处理(将虚拟 DOM 事件映射为浏览器原生事件)。
- 样式、属性的处理(如
class
、style
绑定的 DOM 实现)。
关系:runtime-dom
依赖 runtime-core
,并在其基础上添加了浏览器特有的功能。开发 Web 应用时,通常引入的 vue
包已包含两者;开发跨平台应用时,可能直接使用 runtime-core
配合特定平台的运行时(如 @vue/runtime-mini-program
)。
97. Vue3 中如何使用 provide
/inject
实现跨层级组件通信并保持响应式?
provide
和 inject
用于跨层级组件通信(如祖父组件向孙子组件传递数据),结合响应式 API 可保持数据的响应式:
实现步骤:
-
祖先组件使用
provide
提供响应式数据:import { provide, ref, reactive } from 'vue'; const count = ref(0); // ref 响应式数据 const user = reactive({ name: 'Vue3' }); // reactive 响应式对象// 提供数据(key 为标识,value 为响应式数据) provide('countKey', count); provide('userKey', user);
-
后代组件使用
inject
接收数据并保持响应式:import { inject } from 'vue';// 接收响应式数据(第二个参数为默认值) const count = inject('countKey', ref(0)); const user = inject('userKey', reactive({ name: '默认名称' }));// 使用数据(修改会触发祖先组件及其他依赖组件的更新) const increment = () => { count.value++; }; const changeName = () => { user.name = 'Vue'; };
-
传递修改方法(推荐):
- 为避免后代组件直接修改祖先数据导致逻辑混乱,可提供修改方法:
// 祖先组件 const count = ref(0); const incrementCount = () => { count.value++; }; provide('countKey', { count, incrementCount });// 后代组件 const { count, incrementCount } = inject('countKey'); // 调用方法修改,而非直接修改 count.value incrementCount();
注意:provide
传递的若为响应式数据(ref
/reactive
),inject
接收后仍保持响应式;若传递非响应式数据,修改后后代组件不会感知,因此需传递响应式数据以保持通信。
98. 请描述 Vue3 中模板编译的优化策略?
Vue3 的模板编译阶段进行了多项优化,减少运行时的虚拟 DOM 对比开销,提升渲染性能,主要策略如下:
-
静态提升(Static Hoisting):
- 将模板中的静态内容(如固定文本、无绑定的元素)提取到渲染函数外,仅编译一次,避免每次渲染重新创建虚拟节点。
- 示例:
<!-- 静态内容会被提升 --> <div><h1>静态标题</h1><p>{{ dynamicText }}</p> </div>
-
Patch Flag(补丁标记):
- 对动态节点添加标记(如
/* TEXT */
、/* CLASS */
),标记节点的动态部分(文本、类名、属性等)。 - 运行时对比虚拟 DOM 时,仅需关注带标记的动态节点及其标记的动态部分,跳过静态节点,减少对比时间。
- 示例:动态文本节点会被标记为
TEXT
,更新时仅检查文本内容。
- 对动态节点添加标记(如
-
缓存事件处理函数:
- 对模板中的事件处理函数(如
@click="handleClick"
)进行缓存,避免每次渲染创建新的函数实例,减少虚拟 DOM 的不必要更新。
- 对模板中的事件处理函数(如
-
Block 树优化:
- 将模板按动态节点的分布划分为多个
Block
(块),每个Block
包含一组相邻的动态节点。 - 运行时更新时,仅需遍历包含动态节点的
Block
,进一步缩小对比范围。
- 将模板按动态节点的分布划分为多个
-
条件编译:
- 根据不同的环境(如开发/生产)或特性(如是否支持
Proxy
)生成不同的编译结果,优化运行时代码。
- 根据不同的环境(如开发/生产)或特性(如是否支持
这些优化使 Vue3 的渲染性能相比 Vue2 提升约 55%(官方数据),尤其在大型应用中效果显著。
99. Vue3 中如何使用 Suspense
结合异步组件和异步数据加载?
Suspense
可同时处理异步组件和组件内部的异步数据加载,统一管理加载状态,提升用户体验:
实现步骤:
-
定义异步组件(包含异步数据加载):
<!-- AsyncDataComponent.vue --> <template><div><h2>{{ user.name }}</h2><p>年龄:{{ user.age }}</p></div> </template><script setup> // 异步加载数据(setup 中使用 await) const fetchUser = async () => {const res = await fetch('/api/user');return res.json(); }; const user = await fetchUser(); // 异步数据 </script>
-
在父组件中用
Suspense
包裹异步组件:<template><Suspense><!-- 默认插槽:异步内容(组件 + 数据) --><template #default><AsyncDataComponent /></template><!-- fallback 插槽:加载中状态 --><template #fallback><div class="loading"><span>加载用户数据中...</span><div class="spinner"></div></div></template></Suspense> </template><script setup> // 异步导入组件(结合动态导入) import { defineAsyncComponent } from 'vue'; const AsyncDataComponent = defineAsyncComponent(() => import('./AsyncDataComponent.vue') ); </script>
工作流程:
- 父组件加载时,
Suspense
先显示fallback
插槽的加载状态。 - 同时触发两个异步操作:异步组件的加载(
defineAsyncComponent
)和组件内部的数据加载(await fetchUser()
)。 - 所有异步操作完成后,
Suspense
切换到default
插槽,显示加载完成的内容。
注意:若任一异步操作失败,需通过错误边界(onErrorCaptured
)捕获错误,避免应用崩溃。
100. 请解释 Vue3 中 ref
的实现原理?
ref
是 Vue3 中用于创建响应式数据的核心 API,支持基本类型和对象,其实现原理如下:
-
包装基本类型:
- 对于基本类型(
string
、number
等),ref
将其包装为一个包含value
属性的对象(RefImpl
实例)。 value
属性通过getter
和setter
实现响应式:getter
:访问value
时,触发依赖收集(track
),记录使用该值的副作用函数。setter
:修改value
时,触发依赖更新(trigger
),执行所有关联的副作用函数(如组件渲染)。
- 对于基本类型(
-
处理对象类型:
- 若传入
ref
的是对象或数组,ref
会自动调用reactive
将其转为响应式对象(Proxy
代理)。 - 此时
ref.value
指向该响应式对象,访问或修改对象的属性时,由reactive
的 Proxy 机制处理响应式。
- 若传入
-
自动解包:
- 在模板中使用
ref
时,Vue 会自动解包(无需.value
),直接访问值。 - 在
reactive
对象中访问ref
属性时,也会自动解包:const count = ref(0); const obj = reactive({ count }); console.log(obj.count); // 0(自动解包,等价于 count.value)
- 在模板中使用
简化伪代码:
class RefImpl {constructor(value) {// 若为对象,转为 reactivethis._value = isObject(value) ? reactive(value) : value;}get value() {track(this, 'get', 'value'); // 收集依赖return this._value;}set value(newValue) {if (newValue !== this._value) {this._value = isObject(newValue) ? reactive(newValue) : newValue;trigger(this, 'set', 'value'); // 触发更新}}
}function ref(value) {return new RefImpl(value);
}
总结:ref
通过包装对象的 value
属性实现基本类型的响应式,通过 reactive
处理对象类型,同时支持自动解包以简化使用。
101. Vue3 中如何实现组件的懒加载及代码分割?
组件懒加载通过动态导入(import()
)实现,配合打包工具(如 Webpack、Vite)的代码分割功能,将组件代码拆分为单独的 chunk,按需加载,减少初始加载体积。
实现方式:
-
路由级懒加载(最常用):
// router/index.js import { createRouter, createWebHistory } from 'vue-router';// 动态导入组件,打包时会分割为单独的 chunk const Home = () => import('../views/Home.vue'); const About = () => import('../views/About.vue');// 更精细的分割:指定 chunk 名称(Webpack 语法) const User = () => import(/* webpackChunkName: "user" */ '../views/User.vue');const routes = [{ path: '/', component: Home },{ path: '/about', component: About },{ path: '/user', component: User } ]; const router = createRouter({ history: createWebHistory(), routes });
-
组件级懒加载(非路由组件):
import { defineAsyncComponent } from 'vue';// 懒加载组件,支持加载状态和错误处理 const LazyComponent = defineAsyncComponent({loader: () => import('./LazyComponent.vue'), // 动态导入loadingComponent: Loading, // 加载中显示的组件errorComponent: Error, // 加载失败显示的组件delay: 200 // 延迟 200ms 显示加载组件(避免闪烁) });
-
在模板中使用懒加载组件:
<template><button @click="showLazy = true">显示懒加载组件</button><LazyComponent v-if="showLazy" /> </template> <script setup> import { ref } from 'vue'; import LazyComponent from './LazyComponent'; // 导入懒加载组件 const showLazy = ref(false); </script>
优势:
- 减少初始加载的 JavaScript 体积,提升首屏加载速度。
- 按需加载组件代码,节省带宽和资源。
注意:懒加载组件会增加用户交互时的加载延迟,需配合加载状态提示(如 defineAsyncComponent
的 loadingComponent
)优化体验。
102. 请对比 Vue3 与 Vue2 的虚拟 DOM 差异?
Vue3 对虚拟 DOM 进行了重构和优化,相比 Vue2 有以下差异:
特性 | Vue3 虚拟 DOM | Vue2 虚拟 DOM |
---|---|---|
结构优化 | 采用扁平化的虚拟 DOM 结构,减少嵌套层级 | 嵌套层级较深,递归对比开销大 |
编译时优化 | 结合模板编译,生成带 Patch Flag 的虚拟节点 | 纯运行时对比,无编译时优化 |
对比范围 | 仅对比带 Patch Flag 的动态节点及标记的动态部分 | 全量对比所有节点,包括静态节点 |
性能提升 | 大型应用中渲染性能提升约 55%(官方数据) | 渲染性能随节点数量增加下降较明显 |
数组处理 | 优化数组更新,减少不必要的元素移动 | 数组更新时可能导致大量元素重新渲染 |
Tree-shaking | 支持 Tree-shaking,仅打包使用的虚拟 DOM 方法 | 虚拟 DOM 方法打包时无法剔除,体积较大 |
核心优化点:
- Vue3 通过 Patch Flag 标记动态节点的动态部分(如文本、属性),运行时仅对比这些部分,跳过静态节点。
- Vue3 的虚拟 DOM 生成逻辑与模板编译深度结合,编译阶段即可确定节点的动态特性,减少运行时计算。
- Vue3 对虚拟节点的创建进行了优化,避免不必要的对象创建和属性复制。
示例:
- Vue3 中带 Patch Flag 的虚拟节点(编译后):
// 动态文本节点被标记为 TEXT createVNode('p', null, [createTextVNode(`${msg}`, 1 /* TEXT */)]);
- 运行时对比时,仅检查标记为
TEXT
的节点的文本内容,忽略其他静态部分。
103. Vue3 中如何使用 v-slot
定义和使用插槽?
v-slot
是 Vue 中用于定义和使用插槽的指令,替代了 Vue2 中的 slot
和 slot-scope
,Vue3 中使用方式如下:
-
默认插槽:
- 子组件定义默认插槽:
<!-- Child.vue --> <template><div class="child"><slot></slot> <!-- 默认插槽出口 --></div> </template>
- 父组件使用默认插槽:
<Child><template v-slot:default> <!-- v-slot:default 可省略 --><p>这是默认插槽的内容</p></template> </Child> <!-- 简化写法 --> <Child><p>这是默认插槽的内容</p> </Child>
- 子组件定义默认插槽:
-
具名插槽(多个插槽区分):
- 子组件定义具名插槽:
<!-- Child.vue --> <template><div><slot name="header"></slot> <!-- 头部插槽 --><slot></slot> <!-- 默认插槽 --><slot name="footer"></slot> <!-- 底部插槽 --></div> </template>
- 父组件使用具名插槽:
<Child><template v-slot:header> <!-- 头部内容 --><h1>标题</h1></template><p>正文内容(默认插槽)</p><template v-slot:footer> <!-- 底部内容 --><p>版权信息</p></template> </Child>
- 子组件定义具名插槽:
-
作用域插槽(子组件向父组件传递数据):
- 子组件在插槽中传递数据:
<!-- Child.vue --> <template><div><slot name="item" :data="user" :index="0"></slot></div> </template> <script setup> import { reactive } from 'vue'; const user = reactive({ name: 'Vue3' }); </script>
- 父组件接收插槽数据:
<Child><template v-slot:item="slotProps"> <!-- slotProps 接收数据 --><p>名称:{{ slotProps.data.name }}</p><p>索引:{{ slotProps.index }}</p></template> </Child> <!-- 解构简化 --> <template v-slot:item="{ data, index }"><p>名称:{{ data.name }},索引:{{ index }}</p> </template>
- 子组件在插槽中传递数据:
简化语法:v-slot:name
可缩写为 #name
,如 #header
等价于 v-slot:header
。
104. 请描述 Vue3 中 reactive
的实现原理?
reactive
是 Vue3 中用于创建响应式对象的 API,基于 ES6 的 Proxy
实现,其核心原理如下:
-
Proxy 代理对象:
reactive
接收一个对象,返回该对象的Proxy
代理实例。Proxy
可拦截对象的多种操作(get
、set
、deleteProperty
等),从而实现响应式。
-
依赖收集(
get
拦截):- 当访问代理对象的属性(如
obj.name
)时,get
拦截器被触发。 - 此时调用
track
函数,收集当前活跃的副作用函数(如组件渲染函数、watch
回调),建立属性与副作用函数的依赖关系。
- 当访问代理对象的属性(如
-
触发更新(
set
/deleteProperty
拦截):- 当修改属性(
obj.name = 'new'
)或删除属性(delete obj.name
)时,set
或deleteProperty
拦截器被触发。 - 此时调用
trigger
函数,找到该属性的所有依赖的副作用函数并执行,触发组件更新或回调函数执行。
- 当修改属性(
-
递归代理嵌套对象:
- 当访问对象的嵌套属性(如
obj.user.name
)时,get
拦截器会检查该属性是否为对象,若是则递归调用reactive
使其成为响应式对象(懒代理)。
- 当访问对象的嵌套属性(如
简化伪代码:
function reactive(target) {return new Proxy(target, {get(target, key, receiver) {// 递归代理嵌套对象const value = Reflect.get(target, key, receiver);if (isObject(value)) {return reactive(value);}// 收集依赖track(target, key);return value;},set(target, key, value, receiver) {const oldValue = Reflect.get(target, key, receiver);if (value !== oldValue) {Reflect.set(target, key, value, receiver);// 触发更新trigger(target, key);}return true;},deleteProperty(target, key) {Reflect.deleteProperty(target, key);// 触发更新trigger(target, key);return true;}});
}
注意:
reactive
仅支持对象和数组,不支持基本类型(基本类型需用ref
)。- 代理对象与原对象不相等(
reactive(obj) !== obj
),需使用代理对象才能触发响应式。
105. Vue3 中如何实现基于路由的权限控制?
基于路由的权限控制指根据用户权限动态显示或隐藏路由,阻止未授权用户访问受保护的页面。Vue3 中结合 Vue Router 实现方式如下:
-
定义路由元信息(
meta
):// router/index.js const routes = [{ path: '/login', component: Login },{ path: '/403', component: Forbidden }, // 无权限页面{path: '/dashboard',component: Dashboard,meta: { requiresAuth: true, roles: ['admin', 'editor'] } // 需认证和特定角色},{path: '/admin',component: Admin,meta: { requiresAuth: true, roles: ['admin'] } // 仅 admin 可访问} ];
-
全局前置守卫验证权限:
import { createRouter } from 'vue-router'; const router = createRouter({ /* 配置 */ });router.beforeEach((to, from, next) => {// 1. 无需认证的路由直接放行(如登录页)if (!to.meta.requiresAuth) {next();return;}// 2. 需认证:检查用户是否登录const isLogin = localStorage.getItem('token');if (!isLogin) {next('/login?redirect=' + to.path); // 未登录,跳转到登录页并记录目标路径return;}// 3. 检查角色权限const userRoles = JSON.parse(localStorage.getItem('roles')) || [];const requiredRoles = to.meta.roles || [];if (requiredRoles.length === 0 || requiredRoles.some(role => userRoles.includes(role))) {next(); // 权限匹配,放行} else {next('/403'); // 无权限,跳转到 403 页面} });
-
动态生成路由(基于用户权限):
- 对于权限差异大的场景,可在登录后根据用户权限动态添加路由:
// 登录成功后 const addRoutesByRole = (roles) => {const dynamicRoutes = roles.includes('admin') ? [/* admin 专属路由 */] : [/* 普通用户路由 */];dynamicRoutes.forEach(route => {router.addRoute(route); // 动态添加路由}); };
- 对于权限差异大的场景,可在登录后根据用户权限动态添加路由:
-
在组件中隐藏无权限的导航:
<!-- 导航组件 --> <template><nav><router-link to="/dashboard" v-if="hasPermission(['admin', 'editor'])">仪表盘</router-link><router-link to="/admin" v-if="hasPermission(['admin'])">管理员页面</router-link></nav> </template> <script setup> const hasPermission = (requiredRoles) => {const userRoles = JSON.parse(localStorage.getItem('roles')) || [];return requiredRoles.some(role => userRoles.includes(role)); }; </script>
核心逻辑:通过路由元信息标记权限要求,全局守卫验证登录状态和角色权限,动态控制路由访问和导航显示,确保未授权用户无法访问受保护资源。
二、120道面试题目录列表
文章序号 | vue3面试题120道 |
---|---|
1 | vue3 面试题及详细答案(01 - 15) |
2 | vue3 面试题及详细答案(16 - 30) |
3 | vue3 面试题及详细答案(31 - 45) |
4 | vue3 面试题及详细答案(46 - 60) |
5 | vue3 面试题及详细答案(61 - 75) |
6 | vue3 面试题及详细答案(76 - 90) |
7 | vue3 面试题及详细答案(91 - 105) |
8 | vue3 面试题及详细答案(106 - 120) |