一、Vue3 核心基础篇
1. Vue3 相比 Vue2 有哪些核心优势和重大变更?
核心优势:
- 性能大幅提升:虚拟DOM重写、编译优化(静态提升、PatchFlags)、内存占用减少约50%,渲染速度提升约1.3-2倍。
- 更好的TypeScript支持:源码使用TS重构,提供完整的类型定义,开发中类型推导更精准,减少运行时错误。
- 组合式API(Composition API):替代Options API的部分场景,更好地组织复用复杂逻辑,解决大型组件的"选项碎片化"问题。
- 更小的打包体积:支持Tree Shaking,未使用的API不会被打包,默认打包体积比Vue2小约40%。
- 新增更多实用特性:Teleport(瞬移组件)、Suspense(异步加载兜底)、多根节点组件(Fragment)等。
重大变更:
- 移除
new Vue(),改用createApp()创建应用实例,隔离应用上下文,避免全局污染。 - 移除
Vue.prototype,改用app.config.globalProperties挂载全局属性/方法。 - 过滤器(Filter)被移除,推荐使用计算属性或工具函数替代。
v-model语法变更,移除.sync修饰符,统一为v-model+参数的形式。- 生命周期钩子调整,新增
setup(),移除beforeCreate和created(被setup()替代)。
2. Vue3 的两种 API 风格分别是什么?各自的适用场景是什么?
Vue3 支持两种官方API风格,且两者可以在项目中共存(推荐统一风格):
(1)选项式API(Options API)
- 写法:以
data、methods、computed、watch等选项组织代码,是Vue2的传统写法。 - 示例:
<template>
<div>{{ count }} {{ doubleCount }}</div>
<button @click="increment">+1</button>
</template>
<script>
export default {
data() {
return { count: 0 }
},
computed: {
doubleCount() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
- 适用场景:
- 小型项目、快速原型开发。
- 新手入门Vue,更容易理解和上手(符合"面向对象"的思维习惯)。
- 简单组件(逻辑少、代码量少,无需复杂复用)。
(2)组合式API(Composition API)
- 写法:以
setup()入口(或<script setup>语法糖)为核心,使用ref、reactive等函数组织逻辑,按功能维度聚合代码。 - 示例(
<script setup>语法糖):
<template>
<div>{{ count }} {{ doubleCount }}</div>
<button @click="increment">+1</button>
</template>
<script setup>
import { ref, computed } from 'vue'
// 响应式数据
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => {
count.value++
}
</script>
- 适用场景:
- 中大型项目、复杂组件(逻辑多、需要按功能拆分)。
- 逻辑需要跨组件复用(可提取为Composables工具函数)。
- 追求更好的TypeScript支持,希望获得更精准的类型推导。
- 团队协作项目,便于代码维护和迭代。
3. Vue3 中如何创建响应式数据?ref 和 reactive 的区别是什么?
Vue3 提供ref和reactive两个核心API创建响应式数据,底层基于Proxy(替代Vue2的Object.defineProperty)实现响应式。
(1)创建响应式数据的方式
ref:用于创建基本类型(String、Number、Boolean等)的响应式数据,也可用于引用类型。import { ref } from 'vue' // 基本类型响应式 const name = ref('Vue3') const age = ref(3) // 引用类型响应式 const user = ref({ id: 1, username: 'admin' }) // 修改ref数据:需要通过 .value 访问(模板中可省略 .value) name.value = 'Vue3 Composition API' user.value.username = 'root'reactive:用于创建引用类型(Object、Array等)的响应式数据,无法直接处理基本类型。import { reactive } from 'vue' // 对象响应式 const user = reactive({ id: 1, username: 'admin' }) // 数组响应式 const list = reactive([1, 2, 3, 4]) // 修改reactive数据:直接访问属性/索引即可,无需 .value user.username = 'root' list.push(5)
(2)ref 和 reactive 的核心区别
| 对比维度 | ref | reactive |
|---|---|---|
| 支持数据类型 | 基本类型、引用类型 | 仅支持引用类型(Object/Array等) |
| 访问/修改方式 | 脚本中需通过 .value,模板中可省略 | 直接访问/修改属性/索引,无需 .value |
| 响应式原理 | 底层封装了reactive,通过一个包含value属性的对象实现响应式 | 直接基于Proxy代理整个对象,拦截属性的读取/修改 |
| 解构特性 | 直接解构会丢失响应式(需使用toRefs/toRef) | 直接解构会丢失响应式(需使用toRefs/toRef) |
| 使用场景 | 优先用于基本类型,或简单引用类型 | 优先用于复杂对象、数组,需要统一管理多个相关属性 |
(3)补充:toRefs 和 toRef 的作用
当需要解构reactive或ref(引用类型)的响应式数据,同时保留响应式时,可使用toRefs(批量转换)或toRef(单个转换):
import { reactive, toRefs, toRef } from 'vue'
const user = reactive({
name: 'admin',
age: 20
})
// 批量转换为ref,解构后仍保留响应式
const { name, age } = toRefs(user)
// 单个转换为ref
const nameRef = toRef(user, 'name')
二、组合式API 进阶篇
1. <script setup> 语法糖有哪些优势?它和普通 setup() 有什么区别?
<script setup> 是Vue3.2+推出的组合式API语法糖,是目前组合式API的推荐写法,相比普通setup()有显著优势。
(1)核心优势
- 更少的样板代码:无需导出(
export default),无需在setup()中返回变量/方法,直接声明即可在模板中使用。 - 更好的语法体验:支持顶层
await、自动注册组件(导入后直接使用,无需在components选项中注册)。 - 更优的性能:编译阶段进行优化,比普通
setup()运行效率更高。 - 更好的TypeScript支持:无需额外配置,类型推导更简洁、精准。
(2)与普通 setup() 的区别
| 对比维度 | <script setup> | 普通 setup() |
|---|---|---|
| 写法形式 | 标签上添加 setup 属性,<script setup> | 作为组件选项,export default { setup() {} } |
| 变量/方法暴露 | 顶层声明直接暴露给模板,无需返回 | 需在setup()函数中返回,模板才能访问 |
| 组件注册 | 导入后直接使用,自动注册 | 需手动在components选项中注册(或全局注册) |
this 指向 | 无this(this为undefined) | 无this(与Vue2不同,setup()中this不指向组件实例) |
顶层await | 支持(自动将组件标记为异步组件) | 不支持(需返回Promise,配合Suspense使用) |
| 额外API | 提供defineProps、defineEmits等编译宏,简化父子组件通信 | 需通过setup()的参数(props、context)获取props和emits |
(3)<script setup> 简单示例
<template>
<div>{{ msg }}</div>
<ChildComponent @child-click="handleChildClick" />
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 顶层声明,直接暴露给模板
const msg = ref('Hello <script setup>')
// 方法直接暴露
const handleChildClick = (data) => {
console.log('接收子组件事件:', data)
}
// 顶层await 支持
const data = await fetch('/api/list').then(res => res.json())
</script>
2. Vue3 生命周期钩子有哪些变化?组合式API中如何使用生命周期?
(1)Vue3 生命周期钩子的变化
- 基于组合式API,新增
setup()钩子,替代了Vue2的beforeCreate和created(setup()在这两个钩子之前执行,无需再单独声明这两个钩子)。 - 部分钩子名称调整:
beforeDestroy→beforeUnmount,destroyed→unmounted。 - 新增两个生命周期钩子:
onRenderTracked(跟踪虚拟DOM渲染时的依赖)、onRenderTriggered(触发虚拟DOM重新渲染时的依赖),仅用于调试。 - 选项式API的生命周期钩子仍可使用(向下兼容),但组合式API中推荐使用对应的生命周期函数。
(2)生命周期钩子对应关系(Vue2 → Vue3)
| Vue2 选项式API | Vue3 选项式API | Vue3 组合式API(需导入) | 执行时机 |
|---|---|---|---|
beforeCreate | beforeCreate | -(被setup()替代) | 组件实例创建之前,数据未初始化、方法未挂载 |
created | created | -(被setup()替代) | 组件实例创建完成,数据已初始化、方法已挂载,但DOM未生成 |
beforeMount | beforeMount | onBeforeMount | 组件挂载之前,模板已编译完成,即将渲染DOM |
mounted | mounted | onMounted | 组件挂载完成,DOM已生成,可访问/操作DOM |
beforeUpdate | beforeUpdate | onBeforeUpdate | 组件数据更新之前,虚拟DOM即将重新渲染 |
updated | updated | onUpdated | 组件数据更新完成,虚拟DOM已重新渲染,DOM已更新 |
beforeDestroy | beforeUnmount | onBeforeUnmount | 组件卸载之前,组件实例仍可用,可清理定时器、事件监听等 |
destroyed | unmounted | onUnmounted | 组件卸载完成,组件实例被销毁,所有资源已释放 |
(3)组合式API中使用生命周期(<script setup> 示例)
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUnmounted } from 'vue'
const count = ref(0)
let timer = null
// 组件挂载前
onBeforeMount(() => {
console.log('组件即将挂载')
})
// 组件挂载后
onMounted(() => {
console.log('组件挂载完成')
// 开启定时器
timer = setInterval(() => {
count.value++
}, 1000)
})
// 组件更新前
onBeforeUpdate(() => {
console.log('组件即将更新')
})
// 组件卸载前:清理资源
onUnmounted(() => {
console.log('组件即将卸载')
clearInterval(timer)
})
</script>
3. computed 和 watch 的使用场景分别是什么?watch 如何监听多个数据源?
(1)computed:计算属性(依赖缓存,同步推导)
- 核心特性:基于依赖的响应式数据进行同步计算,具有缓存机制——只有依赖的数据发生变化时,才会重新计算,否则直接返回缓存结果。
- 适用场景:
- 对现有响应式数据进行格式化、转换(如:将时间戳转为格式化日期、将数字转为金额格式)。
- 多个响应式数据的组合计算(如:总价 = 单价 × 数量)。
- 需要依赖缓存,避免重复计算的场景(如:复杂的列表筛选、排序)。
- 使用示例:
import { ref, computed } from 'vue'
const price = ref(100)
const quantity = ref(5)
// 只读计算属性
const totalPrice = computed(() => {
return price.value * quantity.value
})
// 可写计算属性(需传入get和set方法)
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value) {
const [first, last] = value.split(' ')
firstName.value = first
lastName.value = last
}
})
(2)watch:监听器(响应式回调,支持异步)
- 核心特性:监听一个或多个响应式数据的变化,触发自定义回调函数,支持异步操作(如:接口请求、定时器),无缓存机制——只要监听的数据源变化,就会触发回调。
- 适用场景:
- 数据源变化后执行异步操作(如:输入框输入完成后请求搜索接口、ID变化后请求详情数据)。
- 数据源变化后执行复杂的业务逻辑(如:数据变化后更新本地存储、触发全局事件)。
- 监听数据的变化轨迹(如:记录数据的旧值和新值)。
- 基本使用(监听单个数据源):
import { ref, watch } from 'vue'
const name = ref('Vue3')
// 监听ref数据
watch(name, (newVal, oldVal) => {
console.log('名称变化:', oldVal, '→', newVal)
// 支持异步操作
fetch(`/api/search?name=${newVal}`).then(res => res.json())
})
// 监听reactive数据的单个属性
const user = reactive({ id: 1, name: 'admin' })
watch(() => user.id, (newId, oldId) => {
console.log('用户ID变化:', oldId, '→', newId)
})
(3)watch 监听多个数据源
监听多个数据源时,将数据源放入数组中,回调函数的第一个参数为对应数据源的新值数组,第二个参数为旧值数组:
import { ref, reactive, watch } from 'vue'
// 场景1:监听多个ref数据
const name = ref('Vue3')
const age = ref(3)
watch([name, age], ([newName, newAge], [oldName, oldAge]) => {
console.log('名称变化:', oldName, '→', newName)
console.log('年龄变化:', oldAge, '→', newAge)
})
// 场景2:监听多个不同类型数据源(ref + reactive属性)
const user = reactive({ id: 1, username: 'admin' })
const keyword = ref('')
watch([() => user.id, keyword], ([newId, newKeyword], [oldId, oldKeyword]) => {
console.log('用户ID变化:', oldId, '→', newId)
console.log('搜索关键词变化:', oldKeyword, '→', newKeyword)
// 执行异步业务逻辑
fetch(`/api/user/${newId}?keyword=${newKeyword}`).then(res => res.json())
})
(4)补充:watchEffect 与 watch 的区别
watchEffect 是简化版watch,自动收集依赖,无需手动指定数据源,适用于监听多个不确定的数据源变化:
import { ref, watchEffect } from 'vue'
const name = ref('Vue3')
const age = ref(3)
watchEffect(() => {
// 自动收集name和age作为依赖
console.log(`名称:${name.value},年龄:${age.value}`)
})
三、组件开发与通信篇
1. Vue3 中父子组件如何通信?有哪些方式?
Vue3 中父子组件通信的核心逻辑与Vue2一致,只是部分API有调整,主要通信方式如下:
(1)父传子:props
- 子组件:通过
defineProps(<script setup>)或props选项(选项式API)声明接收的属性。 - 父组件:通过属性绑定的方式传递数据给子组件。
- 示例(
<script setup>):- 子组件(Child.vue):
<template> <div>父组件传递的名称:{{ name }},年龄:{{ age }}</div> </template> <script setup> // 声明props(支持类型校验、默认值) const props = defineProps({ name: { type: String, required: true, default: '' }, age: { type: Number, default: 0 } }) // 访问props:直接使用 props.name / props.age console.log(props.name) </script>- 父组件(Parent.vue):
<template> <Child :name="parentName" :age="parentAge" /> </template> <script setup> import { ref } from 'vue' import Child from './Child.vue' const parentName = ref('Vue3 父子通信') const parentAge = ref(3) </script>
(2)子传父:emits 自定义事件
- 子组件:通过
defineEmits声明触发的事件,通过emit方法触发事件并传递数据。 - 父组件:通过
@事件名绑定回调函数,接收子组件传递的数据。 - 示例(
<script setup>):- 子组件(Child.vue):
<template> <button @click="handleClick">向父组件传递数据</button> </template> <script setup> // 声明要触发的事件 const emit = defineEmits(['child-click', 'child-change']) const handleClick = () => { // 触发事件并传递数据(可传递多个参数) emit('child-click', { id: 1, msg: '子组件触发的事件' }) } </script>- 父组件(Parent.vue):
<template> <Child @child-click="handleChildClick" /> </template> <script setup> import Child from './Child.vue' const handleChildClick = (data) => { console.log('接收子组件数据:', data) // { id: 1, msg: '子组件触发的事件' } } </script>
(3)其他补充通信方式
v-model双向绑定:简化父子组件双向通信,替代Vue2的.sync修饰符。// 子组件:声明modelValue props 和 update:modelValue 事件 <script setup> const props = defineProps(['modelValue']) const emit = defineEmits(['update:modelValue']) </script> // 父组件:直接使用v-model绑定 <Child v-model="parentValue" />provide/inject:跨层级组件通信(爷孙组件、深层组件),父组件通过provide提供数据,子孙组件通过inject注入数据。// 父组件:提供数据 import { provide, ref } from 'vue' const theme = ref('dark') provide('theme', theme) // 子孙组件:注入数据 import { inject } from 'vue' const theme = inject('theme')- 全局事件总线:适用于任意组件通信,Vue3中可通过
mitt库实现(替代Vue2的Vue.prototype.$bus)。
2. Vue3 中新增的 Teleport 和 Suspense 组件的作用是什么?如何使用?
(1)Teleport:瞬移组件(组件DOM节点转移)
- 核心作用:将组件的DOM节点脱离当前组件的DOM层级,挂载到页面的指定DOM节点下,同时保留组件的响应式和组件间通信能力(不受DOM层级影响)。
- 解决的问题:
- 弹窗、模态框等组件被父组件的
overflow: hidden、z-index等样式限制,无法正常显示。 - 避免组件DOM层级过深,导致样式调试复杂。
- 弹窗、模态框等组件被父组件的
- 使用示例:
<template>
<div>
<button @click="showModal = true">打开弹窗</button>
<!-- Teleport:将弹窗挂载到body标签下 -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<h3>这是一个瞬移弹窗</h3>
<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;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-content {
background: #fff;
padding: 20px;
border-radius: 8px;
}
</style>
- 关键说明:
to属性指定挂载目标(可传入CSS选择器,如#app、.modal-container,或直接传入DOM节点)。
(2)Suspense:异步组件兜底(等待异步内容加载)
- 核心作用:用于包裹异步组件或包含
顶层await的组件,在异步内容加载完成前显示兜底内容(如加载动画),加载完成后显示组件内容,提升用户体验。 - 注意事项:
Suspense是一个实验性特性(Vue3正式版已支持,但部分场景仍有优化空间)。- 仅支持组合式API中带
顶层await的组件,或通过defineAsyncComponent定义的异步组件。
- 使用示例:
- 定义异步组件(AsyncComponent.vue):
<script setup> // 顶层await:模拟接口请求 const data = await fetch('/api/list').then(res => res.json()) </script> <template> <div> <h3>异步组件内容</h3> <ul> <li v-for="item in data" :key="item.id">{{ item.name }}</li> </ul> </div> </template>- 父组件中使用
Suspense:
<template> <div> <h2>父组件</h2> <!-- Suspense:default 显示异步组件,fallback 显示兜底内容 --> <Suspense> <template #default> <AsyncComponent /> </template> <template #fallback> <div class="loading">加载中...</div> </template> </Suspense> </div> </template> <script setup> import AsyncComponent from './AsyncComponent.vue' </script> <style scoped> .loading { text-align: center; padding: 20px; color: #666; } </style> - 关键说明:
Suspense提供两个插槽——default(异步加载的组件内容)和fallback(加载中的兜底内容)。
3. Vue3 中如何实现组件的透传属性(attrs)和插槽(Slot)?
(1)透传属性(attrs)
- 核心概念:父组件传递给子组件的属性中,未被子组件
props声明接收的属性,会被归类为透传属性(如class、style、id、自定义属性等)。 - Vue3 中获取透传属性:
<script setup>中:通过useAttrs()API获取透传属性。- 选项式API中:通过
this.$attrs获取。
- 特性:
- 透传属性会自动挂载到子组件的根元素上(无需手动处理)。
- 若子组件有多个根节点,透传属性不会自动挂载,需手动指定挂载目标。
- 使用示例:
<!-- 子组件(Child.vue) -->
<template>
<!-- 透传属性自动挂载到该根元素上 -->
<div class="child">子组件</div>
</template>
<script setup>
import { useAttrs } from 'vue'
// 获取透传属性
const attrs = useAttrs()
console.log(attrs) // 输出父组件传递的未被props声明的属性(如id、data-*等)
</script>
<!-- 父组件(Parent.vue) -->
<template>
<!-- 传递props声明的name,和透传属性id、data-type、class -->
<Child
:name="parentName"
id="child-component"
data-type="test"
class="parent-child"
/>
</template>
(2)插槽(Slot):组件内容分发
Vue3 支持插槽的所有功能(默认插槽、具名插槽、作用域插槽),语法与Vue2基本一致,仅<script setup>中使用作用域插槽时有细微调整。
① 默认插槽:无名称的插槽,用于分发组件的默认内容
<!-- 子组件(Child.vue) -->
<template>
<div class="child">
<!-- 默认插槽出口 -->
<slot>默认兜底内容(父组件未传递内容时显示)</slot>
</div>
</template>
<!-- 父组件(Parent.vue) -->
<template>
<Child>
<!-- 默认插槽内容 -->
<p>这是父组件传递给子组件的默认插槽内容</p>
</Child>
</template>
② 具名插槽:有名称的插槽,用于分发组件的指定区域内容
<!-- 子组件(Child.vue) -->
<template>
<div class="child">
<header>
<!-- 具名插槽:header -->
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- 默认插槽 -->
</main>
<footer>
<!-- 具名插槽:footer -->
<slot name="footer"></slot>
</footer>
</div>
</template>
<!-- 父组件(Parent.vue) -->
<template>
<Child>
<!-- 具名插槽:通过v-slot:name 或 #name 绑定 -->
<template #header>
<h3>这是头部插槽内容</h3>
</template>
<!-- 默认插槽 -->
<p>这是主体默认插槽内容</p>
<!-- 具名插槽:footer -->
<template #footer>
<p>这是底部插槽内容</p>
</template>
</Child>
</template>
③ 作用域插槽:子组件向父组件插槽传递数据,父组件可接收并使用
<!-- 子组件(Child.vue) -->
<template>
<div class="child">
<!-- 作用域插槽:通过v-bind传递数据给父组件 -->
<slot name="list" :list="dataList" :total="total"></slot>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
// 子组件的内部数据
const dataList = reactive([{ id: 1, name: 'Vue3' }, { id: 2, name: 'React' }])
const total = ref(2)
</script>
<!-- 父组件(Parent.vue) -->
<template>
<Child>
<!-- 作用域插槽:接收子组件传递的数据 -->
<template #list="slotProps">
<ul>
<li v-for="item in slotProps.list" :key="item.id">{{ item.name }}</li>
</ul>
<p>总数量:{{ slotProps.total }}</p>
</template>
</Child>
</template>
四、状态管理与路由篇
1. Vue3 中如何使用 Pinia?Pinia 相比 Vuex 有哪些优势?
Pinia 是 Vue 官方推荐的新一代状态管理库,替代了 Vuex(Vuex 4 为 Vue3 适配版本,但后续不再维护),专门为 Vue3 和组合式API设计。
(1)Pinia 的基本使用步骤
- 安装 Pinia:
npm install pinia # 或 yarn add pinia - 在项目入口(
main.js)中创建并挂载 Pinia 实例:import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) const pinia = createPinia() app.use(pinia) app.mount('#app') - 定义 Store(存储):
// src/stores/user.js import { defineStore } from 'pinia' // 定义并导出Store(第一个参数为Store唯一ID,必须唯一) export const useUserStore = defineStore('user', { // 状态:返回初始状态的函数(类似Vue2的data) state: () => ({ username: 'admin', token: '', roles: [] }), // 计算属性:类似Vue的computed,具有缓存机制 getters: { // 接收state作为参数 isAdmin: (state) => state.roles.includes('admin'), // 访问其他getters:通过this(需指定返回值类型) userInfo: function() { return { username: this.username, isAdmin: this.isAdmin } } }, // 方法:类似Vue的methods,用于修改状态(支持异步) actions: { // 修改状态:直接通过this访问state setToken(newToken) { this.token = newToken }, // 异步操作:如接口请求 async login(userForm) { const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify(userForm) }) const data = await res.json() this.token = data.token this.username = data.username return data } } }) - 在组件中使用 Store:
<template> <div> <p>用户名:{{ userStore.username }}</p> <p>是否为管理员:{{ userStore.isAdmin }}</p> <button @click="handleLogin">登录</button> </div> </template> <script setup> import { useUserStore } from '@/stores/user' // 获取Store实例 const userStore = useUserStore() // 调用Store的actions方法 const handleLogin = async () => { await userStore.login({ username: 'root', password: '123456' }) } // 重置Store状态 const resetUserStore = () => { userStore.$reset() } // 批量修改Store状态 const updateUserInfo = () => { userStore.$patch({ username: 'root', roles: ['admin', 'editor'] }) } </script>
(2)Pinia 相比 Vuex 的核心优势
- 更简洁的API:移除了 Vuex 的
Mutation(同步修改状态),所有状态修改都在Actions中完成(支持同步/异步),减少了样板代码。 - 更好的 TypeScript 支持:无需额外配置,类型推导更精准,开发时获得更好的代码提示,减少类型错误。
- 无模块嵌套:Pinia 中没有
modules概念,每个 Store 都是独立的,可直接跨 Store 访问,解决了 Vuex 模块嵌套复杂、命名空间繁琐的问题。 - 轻量化:体积更小(约1KB),打包后对项目体积影响极小。
- 更好的组合式API支持:与 Vue3 的
<script setup>无缝集成,使用更灵活。 - 内置持久化支持:可通过
pinia-plugin-persistedstate插件轻松实现状态持久化(无需手动处理localStorage)。
2. Vue Router 4 (Vue3 适配版)有哪些核心变更?如何实现路由守卫和动态路由?
Vue Router 4 是专门为 Vue3 设计的路由库,与 Vue Router 3(Vue2 适配版)相比有部分核心变更,同时保留了大部分原有API。
(1)Vue Router 4 的核心变更
- 创建路由实例的API变更:
new VueRouter()→createRouter()。 - 路由模式配置变更:
mode: 'history'→history: createWebHistory()(需从vue-router导入对应方法)。 - 移除
base配置:改为在createWebHistory()中传入基础路径(如createWebHistory('/admin/'))。 - 路由守卫的
next()方法可选:Vue Router 4 中,守卫函数可直接返回true/false/路由地址,无需手动调用next()(兼容旧版next())。 - 支持组合式API:新增
useRouter()、useRoute()API,在<script setup>中获取路由实例和当前路由信息。 - 移除
$router和$route的类型扩展:需通过declare module 'vue-router'进行自定义类型扩展。
(2)Vue Router 4 的基本使用
- 安装:
npm install vue-router@4 # 或 yarn add vue-router@4 - 配置路由(
src/router/index.js):import { createRouter, createWebHistory } from 'vue-router' import Home from '@/views/Home.vue' import About from '@/views/About.vue' // 路由规则 const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About // 懒加载组件(推荐) // component: () => import('@/views/About.vue') } ] // 创建路由实例 const router = createRouter({ history: createWebHistory(), // history模式(替代旧版mode: 'history') // hash模式:createWebHashHistory() routes }) export default router - 挂载路由(
src/main.js):import { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app') - 组件中使用路由:
<template> <!-- 路由链接 --> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> <!-- 路由出口 --> <router-view /> </template> <script setup> import { useRouter, useRoute } from 'vue-router' // 获取路由实例 const router = useRouter() // 获取当前路由信息 const route = useRoute() // 编程式导航 const toAbout = () => { router.push('/about') // 或带参数导航 // router.push({ name: 'About', query: { id: 1 } }) } // 访问当前路由参数 console.log(route.query.id) </script>
(3)路由守卫:控制路由的跳转权限
Vue Router 4 支持三种路由守卫:全局守卫、路由独享守卫、组件内守卫。
① 全局守卫(全局生效,如登录校验)
// src/router/index.js
import router from './index'
// 全局前置守卫:路由跳转前触发
router.beforeEach((to, from) => {
// to:目标路由对象
// from:当前即将离开的路由对象
// 示例:登录校验,未登录禁止进入/home
const token = localStorage.getItem('token')
if (to.path === '/home' && !token) {
return '/' // 跳转到首页
}
return true // 允许跳转
})
// 全局后置守卫:路由跳转后触发(无拦截能力,用于统计、埋点等)
router.afterEach((to, from) => {
console.log('路由跳转完成:', from.path, '→', to.path)
})
② 路由独享守卫(仅对当前路由生效)
const routes = [
{
path: '/home',
name: 'Home',
component: () => import('@/views/Home.vue'),
// 路由独享前置守卫
beforeEnter: (to, from) => {
const token = localStorage.getItem('token')
if (!token) {
return '/'
}
}
}
]
③ 组件内守卫(仅对当前组件生效)
<script setup>
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
// 组件内前置守卫:进入组件前触发(与路由独享守卫类似)
// 注:<script setup>中无法直接使用beforeRouteEnter,需通过路由元信息或其他方式替代
// 组件内更新守卫:当前路由参数变化时触发(如 /user/:id 从 /user/1 跳转到 /user/2)
onBeforeRouteUpdate((to, from) => {
console.log('路由参数变化:', from.params.id, '→', to.params.id)
})
// 组件内离开守卫:离开组件时触发
onBeforeRouteLeave((to, from) => {
const confirmLeave = window.confirm('确定要离开当前页面吗?')
if (!confirmLeave) {
return false // 阻止离开
}
})
</script>
(4)动态路由:动态添加/删除路由规则
通过路由实例的addRoute()(添加路由)和removeRoute()(删除路由)方法实现动态路由,常用于权限控制(如根据用户角色动态生成路由)。
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: () => import('@/views/Home.vue') }
]
})
// 动态添加路由(示例:登录后根据角色添加/admin路由)
export const addAdminRoute = () => {
router.addRoute({
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
children: [
{ path: 'user', component: () => import('@/views/Admin/User.vue') }
]
})
}
// 动态删除路由(通过路由name删除)
export const removeAdminRoute = () => {
router.removeRoute('Admin')
}
export default router
在组件中调用动态路由方法:
<script setup>
import { addAdminRoute } from '@/router'
// 登录成功后添加动态路由
const handleLoginSuccess = () => {
addAdminRoute()
// 跳转到动态添加的路由
router.push('/admin')
}
</script>
五、构建部署与优化篇
1. Vue3 项目的构建工具有哪些?如何使用 Vite 搭建 Vue3 项目?
(1)Vue3 支持的构建工具
- Vite:Vue 官方推荐的新一代构建工具,专为 Vue3、React 等前端框架设计,具有“快速冷启动、按需编译、模块热更新”等优势,是 Vue3 项目的首选构建工具。
- Vue CLI:Vue 官方的传统构建工具,支持 Vue3(需升级到 Vue CLI 5+),基于 Webpack 构建,配置成熟、生态完善,适合团队熟悉 Webpack 或需要复杂配置的项目。
- Webpack:手动配置 Webpack 搭建 Vue3 项目,灵活性最高,但配置复杂,适合有丰富 Webpack 经验的开发者。
(2)使用 Vite 搭建 Vue3 项目(推荐)
Vite 搭建 Vue3 项目步骤简单,无需额外配置即可快速启动。
-
初始化项目(Node.js 版本需 ≥ 14.18):
# npm 6.x npm create vite@latest my-vue3-project --template vue # npm 7+(需额外加 --) npm create vite@latest my-vue3-project -- --template vue # yarn yarn create vite my-vue3-project --template vue # pnpm pnpm create vite my-vue3-project --template vue- 模板说明:
vue:Vue3 选项式API 模板。vue-ts:Vue3 + TypeScript 选项式API 模板。vue-jsx:Vue3 + JSX 模板。vue-ts-jsx:Vue3 + TypeScript + JSX 模板。
- 模板说明:
-
进入项目目录并安装依赖:
cd my-vue3-project npm install -
启动开发服务器:
npm run dev- 特性:启动速度极快(秒级启动),修改代码后模块热更新(HMR)即时生效,无需等待整个项目重新打包。
-
构建生产环境包:
npm run build- 构建产物输出到
dist目录,可直接部署到静态服务器。
- 构建产物输出到
-
预览生产环境包:
npm run preview
2. Vue3 项目有哪些常见的性能优化手段?
Vue3 项目的性能优化可从代码层面、构建层面、运行时层面三个维度入手,结合 Vue3 的特性进行针对性优化。
(1)代码层面优化
- 合理使用组合式API:将复杂逻辑提取为 Composables 工具函数,避免组件体积过大,同时提高代码复用率。
- 减少不必要的响应式数据:仅对需要视图更新的数据使用
ref/reactive,普通常量直接声明(无需响应式),减少 Proxy 代理的性能开销。 - 优化
v-for渲染:- 必须添加唯一
key(避免使用索引作为key)。 - 避免在
v-for中使用v-if(可通过计算属性先筛选数据,再渲染)。 - 大数据列表使用虚拟滚动(如
vue-virtual-scroller),只渲染可视区域的DOM节点。
- 必须添加唯一
- 合理使用
computed和watch:- 复杂的格式化逻辑使用
computed(利用缓存减少重复计算)。 - 仅在需要时使用
watch,避免不必要的监听。
- 复杂的格式化逻辑使用
- 组件懒加载:路由组件使用懒加载(
() => import('@/views/XXX.vue')),非路由组件按需导入,减少初始打包体积。 - 避免过度封装组件:简单功能无需封装为独立组件,减少组件通信和DOM层级的开销。
(2)构建层面优化(基于 Vite/Vue CLI)
- 开启 Tree Shaking:Vite/Vue CLI 已默认开启 Tree Shaking,自动移除未使用的代码,减少打包体积。
- 配置路径别名:简化模块导入路径,同时提高构建速度(避免相对路径查找)。
// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': path.resolve(__dirname, './src') } } }) - 压缩资源文件:
- 压缩JS/CSS:Vite 生产环境默认使用
esbuild压缩,可配置terser获得更高压缩率。 - 压缩图片:使用
vite-plugin-imagemin插件压缩图片(JPG/PNG/GIF等)。
- 压缩JS/CSS:Vite 生产环境默认使用
- 拆分第三方依赖:将
vue、pinia、vue-router等第三方依赖拆分为单独的chunk,利用浏览器缓存(避免每次打包都更新第三方依赖的哈希值)。 - 开启Gzip/Brotli压缩:在
vite.config.js中配置compression插件,生成压缩包,减少网络传输体积。
(3)运行时层面优化
- 优化首屏加载:
- 首屏关键资源内联(如
CSS内联到HTML中,避免额外网络请求)。 - 使用
Suspense+ 异步组件,首屏先加载核心内容,非核心内容异步加载。
- 首屏关键资源内联(如
- 缓存优化:
- 利用浏览器强缓存(设置
Cache-Control、Expires响应头)和协商缓存(ETag、Last-Modified)。 - Pinia 状态持久化(
pinia-plugin-persistedstate),避免页面刷新后重新请求数据。
- 利用浏览器强缓存(设置
- 减少DOM操作:
- 利用 Vue 的虚拟DOM,避免手动操作DOM。
- 复杂DOM变更使用
nextTick,确保DOM更新完成后再执行操作。
- 清理无用资源:
- 在组件
onUnmounted钩子中清理定时器、事件监听、WebSocket连接等,避免内存泄漏。 - 避免全局挂载大量无用的属性和方法,减少全局上下文的开销。
- 在组件
- 开启 Vue3 的编译优化:Vue3 已默认开启静态提升、PatchFlags 等编译优化,无需额外配置,确保项目使用最新版本的 Vue3,获得最佳编译优化效果。