一、Vue 3 Composition API
Vue 3 Composition API 是 Vue 3 核心的语法升级,核心目标是解决 Vue 2 Options API 在复杂组件中 “代码碎片化” 的问题,同时提升逻辑复用性、类型推导友好性。相比于 Options API(data、methods、computed、watch 等选项式写法),Composition API 以函数式的方式组织代码,让相关逻辑内聚,而非分散在不同选项中。
1. 核心设计理念
在 Vue 2 中,一个处理表单验证、数据请求、状态监听的复杂组件,其验证逻辑可能分散在 methods、watch、computed 中,代码跳转成本高;而 Composition API 允许将 “表单验证逻辑”“数据请求逻辑” 分别封装成独立函数,再在 setup 中组合,实现 “按逻辑维度组织代码”,而非 “按 API 类型组织代码”。同时,Composition API 天然适配 TypeScript,能提供更精准的类型推导,解决了 Vue 2 中 this 指向模糊、类型提示不友好的问题。
2. 核心语法与使用场景
(1)setup 函数:入口点
setup 是 Composition API 的核心入口,组件创建前执行(在 beforeCreate 钩子前),此时组件实例尚未创建,this 指向 undefined。它接收两个参数:props(组件传入的属性,响应式且不可解构)、context(包含 attrs、slots、emit 等上下文),返回值可直接在模板中使用。
<template>
<div>{{ count }} {{ doubleCount }}</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
// 基础响应式数据
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法
const increment = () => count.value++
// 返回给模板
return { count, doubleCount, increment }
}
}
</script>
Vue 3 中 <script setup> 语法的简写形式,以下是 Vue 3 推荐的更简洁的写法,无需手动导出 setup 函数,代码量更少且更直观。以下是完整的简写版本:
<template>
<div>{{ count }} {{ doubleCount }}</div>
<!-- 可直接调用 increment 方法,比如加个按钮测试 -->
<button @click="increment">+1</button>
</template>
<!-- 使用 setup 语法糖,直接在 script 内写逻辑 -->
<script setup>
import { ref, computed } from 'vue'
// 基础响应式数据(模板中自动可用,无需 return)
const count = ref(0)
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 方法(模板中自动可用)
const increment = () => count.value++
</script>
核心简化点说明:
<script setup>是语法糖,无需定义export default和setup函数,直接在 script 内编写逻辑;- 声明的变量(
count、doubleCount)和方法(increment)无需手动return,会自动暴露给模板使用; - 核心 API(
ref、computed)的使用逻辑完全不变,仅简化了代码结构; - 这是 Vue 3 项目中最主流、最推荐的写法,相比原写法更简洁且易维护。
(2)响应式 API:ref 与 reactive
ref:用于创建基本类型(字符串、数字、布尔)的响应式数据,内部通过包裹对象实现响应式,访问 / 修改需通过.value(模板中自动解包);reactive:用于创建对象 / 数组类型的响应式数据,基于 ES6 Proxy 实现(相比 Vue 2 的 Object.defineProperty 支持数组、新增属性),直接操作属性即可,无需.value。
import { ref, reactive } from 'vue'
// 基本类型响应式
const name = ref('张三')
name.value = '李四' // 修改
// 对象类型响应式
const user = reactive({ age: 20 })
user.age = 21 // 修改
// 解构响应式对象(需用 toRefs 保持响应式)
import { toRefs } from 'vue'
const { age } = toRefs(user)
(3)生命周期钩子:组合式写法
Vue 2 的生命周期钩子在 Composition API 中以 onXXX 形式存在,需从 vue 导入,且只能在 setup 中调用:
| Vue 2 钩子 | Composition API |
|---|---|
| created | setup 内部直接写 |
| mounted | onMounted |
| updated | onUpdated |
| unmounted | onUnmounted |
import { onMounted, onUnmounted } from 'vue'
setup() {
onMounted(() => {
console.log('组件挂载')
})
onUnmounted(() => {
console.log('组件卸载')
})
}
(4)逻辑复用:自定义组合式函数
这是 Composition API 最核心的优势之一。将通用逻辑封装成以 use 开头的函数,实现跨组件复用,替代 Vue 2 的 mixins(mixins 存在命名冲突、来源模糊问题)。
// useFetch.js(封装数据请求逻辑)
import { ref, onMounted } from 'vue'
export function useFetch(url) {
const data = ref(null)
const loading = ref(true)
const error = ref(null)
const fetchData = async () => {
try {
const res = await fetch(url)
data.value = await res.json()
} catch (err) {
error.value = err
} finally {
loading.value = false
}
}
onMounted(fetchData)
return { data, loading, error }
}
// 组件中使用
import { useFetch } from './useFetch'
setup() {
const { data, loading, error } = useFetch('/api/user')
return { data, loading, error }
}
3. 优势与适用场景
- 适合复杂组件:将分散的逻辑聚合,提升可读性和维护性;
- 逻辑复用更优雅:自定义组合式函数替代 mixins、高阶组件,逻辑来源清晰;
- TypeScript 友好:通过类型注解实现精准类型推导;
- 按需导入:减小打包体积(仅导入使用的 API)。
二、Vuex/Pinia 状态管理
在 Vue 应用中,组件间共享状态(如用户信息、全局配置、购物车数据)需要统一的状态管理方案,Vuex(Vue 2 主流)和 Pinia(Vue 3 官方推荐)是核心解决方案,Pinia 可视为 Vuex 5 的替代方案,更轻量、更贴合 Composition API。
1. Vuex:传统状态管理方案
Vuex 基于 “单向数据流” 设计,核心概念包括 State、Getter、Mutation、Action、Module,适用于 Vue 2 或 Vue 3 兼容场景。
(1)核心结构
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 全局状态 🤔🤔🤔
state: {
count: 0,
user: { name: '张三' }
},
// 计算派生状态(类似组件 computed) 🤔🤔🤔
getters: {
doubleCount: state => state.count * 2,
userName: state => state.user.name
},
// 同步修改状态(唯一方式) 🤔🤔🤔
mutations: {
increment(state, payload) {
state.count += payload
},
updateUser(state, name) {
state.user.name = name
}
},
// 异步操作(可提交 mutation) 🤔🤔🤔
actions: {
async incrementAsync({ commit }, payload) {
await new Promise(resolve => setTimeout(resolve, 1000))
commit('increment', payload)
}
},
// 模块拆分(解决状态臃肿) 🤔🤔🤔
modules: {
cart: {
namespaced: true, // 命名空间,避免命名冲突
state: { goods: [] },
mutations: { addGoods(state, goods) { state.goods.push(goods) } }
}
}
})
(2)组件中使用
<template>
<div>{{ $store.state.count }} {{ doubleCount }}</div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 映射 state 🤔🤔🤔
...mapState(['count']),
// 映射 getter 🤔🤔🤔
...mapGetters(['doubleCount'])
},
methods: {
// 映射 mutation 🤔🤔🤔
...mapMutations(['increment']),
// 映射 action 🤔🤔🤔
...mapActions(['incrementAsync']),
handleClick() {
this.increment(1) // 调用 mutation
this.incrementAsync(2) // 调用 action
// 模块内调用(命名空间)
this.$store.commit('cart/addGoods', { id: 1, name: '商品' })
}
}
}
</script>
(3)Vuex 的局限性
- 类型支持差:与 TypeScript 结合需额外配置,类型推导不友好;
- 模块化繁琐:命名空间、模块嵌套增加复杂度;
- 代码冗余:mutation/action 分离,简单场景下写法繁琐;
- Vue 3 适配性:需安装
vuex@4,且与 Composition API 结合时需手动适配。
2. Pinia:Vue 3 官方推荐方案
Pinia 解决了 Vuex 的痛点,去掉了 Mutation(直接在 Action 中修改状态),天然支持 TypeScript,体积更小(约 1KB),且无需嵌套模块,采用扁平化设计。
(1)核心优势
- 无 Mutation:Action 支持同步 / 异步修改状态,简化代码;
- 类型安全:无需额外配置,TypeScript 自动推导类型;
- 模块化更简单:每个 Store 是独立模块,无需命名空间;
- 适配 Composition API:提供
useStore钩子,无缝集成; - 轻量:无依赖,打包体积远小于 Vuex。
(2)基本使用
第一步:安装并注册
npm install pinia
// main.js(Vue 3)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
第二步:定义 Store
// store/counter.js
import { defineStore } from 'pinia'
// 定义并导出 Store(id 唯一,用于 devtools 标识)
export const useCounterStore = defineStore('counter', {
// 状态(类似 Vuex state) 🤔🤔🤔
state: () => ({
count: 0,
user: { name: '张三' }
}),
// 派生状态(类似 Vuex getter) 🤔🤔🤔
getters: {
doubleCount: (state) => state.count * 2,
// 访问其他 getter 或 state
doubleCountPlusOne() {
return this.doubleCount + 1
}
},
// 操作(同步/异步,类似 Vuex action) 🤔🤔🤔
actions: {
increment(payload) {
this.count += payload // 直接修改状态
},
async incrementAsync(payload) {
await new Promise(resolve => setTimeout(resolve, 1000))
this.increment(payload)
},
updateUser(name) {
this.user.name = name
}
}
})
第三步:组件中使用
<template>
<div>{{ counterStore.count }} {{ counterStore.doubleCount }}</div>
</template>
<script setup>
import { useCounterStore } from '@/store/counter'
// 获取 Store 实例(响应式,无需手动解包)
const counterStore = useCounterStore()
// 修改状态
const handleIncrement = () => {
counterStore.increment(1)
counterStore.incrementAsync(2)
}
// 批量修改状态($patch 优化性能)
const batchUpdate = () => {
counterStore.$patch({
count: 10,
user: { name: '李四' }
})
// 或函数式(适合复杂修改)
counterStore.$patch((state) => {
state.count += 5
state.user.name = '王五'
})
}
</script>
(3)Store 间通信
// store/cart.js
import { useUserStore } from './user'
export const useCartStore = defineStore('cart', {
actions: {
addGoods(goods) {
const userStore = useUserStore()
// 实现 用户和购物车 之间通信
if (userStore.isLogin) {
this.goodsList.push(goods)
}
}
}
})
3. 选型建议
- Vue 2 项目:继续使用 Vuex 3,无需迁移;
- Vue 3 新项目:优先使用 Pinia,简化开发、提升类型体验;
- 大型 Vue 3 项目:Pinia 更适合,模块化清晰、维护成本低。
三、Vue Router
Vue Router 是 Vue 官方的路由管理器,用于实现单页面应用(SPA)的路由跳转,核心作用是将 URL 路径与组件映射,实现无刷新页面切换,适配 Vue 2/3 版本(Vue Router 3 对应 Vue 2,Vue Router 4 对应 Vue 3)。
1. 核心概念
SPA 本质是一个 HTML 文件,路由通过监听 URL 变化(hash 模式 /history 模式),动态替换页面内容,Vue Router 封装了这一过程,核心概念包括:
- 路由实例:通过
createRouter创建,配置路由规则、模式等; - 路由规则:定义路径与组件的映射关系;
- 路由出口:
<router-view>,渲染匹配的组件; - 路由导航:
<router-link>(替代 a 标签)或编程式导航(router.push); - 路由守卫:拦截路由跳转,实现权限控制、页面跳转前验证等。
2. Vue Router 4(Vue 3 版本)核心使用
(1)安装与配置
npm install vue-router@4
// router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
// 路由规则
const routes = [
{
path: '/', // 根路径
name: 'Home', // 路由命名
component: Home, // 匹配的组件
meta: { requiresAuth: false } // 自定义元信息(用于守卫)
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue') // 懒加载(按需加载组件)
},
{
path: '/user/:id', // 动态路由参数
name: 'User',
component: () => import('@/views/User.vue'),
props: true // 将路由参数作为 props 传入组件
},
{
path: '/:pathMatch(.*)*', // 404 路由(匹配所有未定义路径)
component: () => import('@/views/404.vue')
}
]
// 创建路由实例
const router = createRouter({
history: createWebHistory(), // history 模式(需后端配置)
// history: createWebHashHistory(), // hash 模式(无需后端配置)
routes
})
export default router
// 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')
(2)模板中使用路由
<!-- App.vue -->
<template>
<div>
<!-- 路由导航(替代 a 标签) -->
<router-link to="/">首页</router-link>
<router-link :to="{ name: 'About' }">关于</router-link>
<router-link :to="{ name: 'User', params: { id: 1 } }">用户1</router-link>
<!-- 路由出口:渲染匹配的组件 -->
<router-view />
</div>
</template>
(3)编程式导航
在组件中通过 useRouter/useRoute 钩子访问路由实例和当前路由信息:
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter() // 路由实例(用于导航)
const route = useRoute() // 当前路由信息(包含 params、query 等)
// 跳转路由
const goToAbout = () => {
// 方式1:路径跳转
router.push('/about')
// 方式2:命名路由(推荐,修改路径无需改代码)
router.push({ name: 'About' })
// 带查询参数:/user/1?name=张三
router.push({ name: 'User', params: { id: 1 }, query: { name: '张三' } })
}
// 替换当前路由(无历史记录)
const replaceToHome = () => {
router.replace('/')
}
// 前进/后退
const goBack = () => {
router.go(-1) // 后退一步
}
// 获取路由参数
console.log(route.params.id) // 动态参数
console.log(route.query.name) // 查询参数
</script>
(4)路由守卫
路由守卫用于控制路由跳转权限,分为全局守卫、路由独享守卫、组件内守卫。
全局守卫(全局生效)
// router/index.js
// 全局前置守卫(跳转前触发)
router.beforeEach((to, from, next) => {
// to:目标路由;from:当前路由;next:继续跳转的函数
// 验证是否需要登录
if (to.meta.requiresAuth && !localStorage.getItem('token')) {
next('/login') // 跳转到登录页
} else {
next() // 正常跳转
}
})
// 全局后置守卫(跳转后触发,无 next)
router.afterEach((to, from) => {
// 记录页面访问日志、修改页面标题等
document.title = to.name || '默认标题'
})
路由独享守卫(仅当前路由生效)
const routes = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
beforeEnter: (to, from, next) => {
// 仅/admin 路由生效的守卫
if (!localStorage.getItem('isAdmin')) {
next('/403')
} else {
next()
}
}
}
]
组件内守卫(仅当前组件生效)
<script setup>
import { onBeforeRouteEnter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'
// 进入组件前触发(此时组件实例未创建,无法访问 this)
onBeforeRouteEnter((to, from, next) => {
next(vm => {
// vm 是组件实例,可访问组件数据
console.log(vm.count)
})
})
// 路由参数更新时触发(如 /user/1 → /user/2)
onBeforeRouteUpdate((to, from, next) => {
console.log('参数更新:', to.params.id)
next()
})
// 离开组件时触发(如确认是否离开表单页面)
onBeforeRouteLeave((to, from, next) => {
const confirm = window.confirm('是否确认离开?')
if (confirm) {
next()
} else {
next(false) // 取消跳转
}
})
</script>
(5)嵌套路由
用于实现页面布局嵌套(如侧边栏 + 主内容):
// 路由规则
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminLayout.vue'),
children: [
{ path: '', component: () => import('@/views/AdminHome.vue') }, // 子路由默认页
{ path: 'user', component: () => import('@/views/AdminUser.vue') }, // /admin/user
{ path: 'order', component: () => import('@/views/AdminOrder.vue') } // /admin/order
]
}
]
<!-- AdminLayout.vue -->
<template>
<div class="admin-layout">
<div class="sidebar">
<router-link to="/admin/user">用户管理</router-link>
<router-link to="/admin/order">订单管理</router-link>
</div>
<div class="main">
<router-view /> <!-- 渲染子路由组件 -->
</div>
</div>
</template>
3. 关键注意事项
- history 模式需后端配置:Nginx/Apache 需配置所有路径指向 index.html,避免刷新 404;
- 懒加载优化性能:通过动态 import 拆分代码包,减小首屏加载体积;
- 路由参数解耦:使用
props: true将参数传入组件,避免组件依赖$route; - 权限控制:优先使用全局前置守卫统一处理登录 / 权限验证。
四、组件通信
组件通信是 Vue 开发的核心基础能力,所有基于组件化拆分的 Vue 应用,都需要通过合理的通信方式实现数据传递、事件触发与状态共享。Vue 围绕 “单向数据流” 核心原则,提供了多套通信方案,适配父子、兄弟、跨层级等不同组件关系场景。理解各类通信方式的原理、适用场景与最佳实践,是保障代码可维护性、数据流可追溯的关键。
1 父子组件通信(核心常用场景)
父子组件是最直接的组件关系,也是通信需求最频繁的场景,Vue 为其设计了一套清晰、规范的通信体系,核心遵循 “父传子靠 Props,子传父靠自定义事件” 的原则。
1.1 父传子:Props 传递数据
Props 是父组件向子组件传递数据的唯一官方推荐方式,本质是子组件对外暴露的 “数据输入口”。Props 具有只读特性,子组件不可直接修改父组件传递的 Props,否则会触发 Vue 警告,这是为了保证单向数据流的清晰性。
在 Vue 3 的 <script setup> 语法中,通过 defineProps 声明接收的 Props,支持类型验证、默认值、必填项等配置,能在开发阶段提前捕获数据类型错误:
<!-- 子组件 -->
<script setup>
// 带类型验证的 Props 声明
const props = defineProps({
// 基本类型
title: {
type: String,
default: '默认标题'
},
// 对象类型(默认值需用函数返回,避免引用共享)
user: {
type: Object,
required: true,
default: () => ({ name: '未知', age: 0 })
}
})
</script>
父组件只需通过属性绑定的方式传递数据,即可完成 “父传子”:
<!-- 父组件 -->
<template>
<Child :title="pageTitle" :user="userInfo" />
</template>
<script setup>
import { ref, reactive } from 'vue'
const pageTitle = ref('用户详情')
const userInfo = reactive({ name: '张三', age: 25 })
</script>
1.2 子传父:自定义事件
子组件无法直接修改父组件数据,需通过 “触发自定义事件” 的方式通知父组件,由父组件自行修改数据。Vue 3 中通过 defineEmits 声明组件可触发的事件,再通过 emit 方法触发并传递参数。
<!-- 子组件 -->
<script setup>
// 声明可触发的事件
const emit = defineEmits(['update-user', 'submit'])
const handleClick = () => {
// 触发事件并传递参数
emit('update-user', { name: '李四', age: 26 })
emit('submit')
}
</script>
父组件通过 @事件名 监听子组件事件,在回调中处理数据更新:
<!-- 父组件 -->
<template>
<Child @update-user="handleUpdateUser" @submit="handleSubmit" />
</template>
<script setup>
const handleUpdateUser = (newUser) => {
// 父组件修改自身数据
Object.assign(userInfo, newUser)
}
const handleSubmit = () => {
console.log('子组件触发了提交事件')
}
</script>
1.3 特殊场景:ref + defineExpose
若父组件需要直接访问子组件的属性或调用子组件的方法,可通过 ref 获取子组件实例,子组件需通过 defineExpose 显式暴露需要对外公开的内容(Vue 3 中组件内部数据默认封闭)。
<!-- 子组件 -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const reset = () => {
count.value = 0
}
// 暴露属性/方法给父组件
defineExpose({ count, reset })
</script>
<!-- 父组件 -->
<template>
<Child ref="childRef" />
</template>
<script setup>
import { ref } from 'vue'
const childRef = ref(null)
// 调用子组件方法
const callChildMethod = () => {
childRef.value.reset()
}
</script>
该方式需谨慎使用,过度依赖会增加组件耦合度,仅适用于 “父组件需主动控制子组件行为” 的特殊场景(如重置子组件表单)。
2 兄弟组件通信
兄弟组件无直接关联,需通过 “中间媒介” 实现通信,核心方案分为三类,适配不同复杂度场景。
2.1 父组件中转(简单场景首选)
以父组件作为中间层,接收一个子组件的事件数据,再通过 Props 将数据传递给另一个子组件。该方式逻辑简单、数据流清晰,适用于兄弟组件通信逻辑较简单的场景。
<!-- 父组件 -->
<template>
<Brother1 @send-data="handleData" />
<Brother2 :data="sharedData" />
</template>
<script setup>
import { ref } from 'vue'
const sharedData = ref('')
// 接收兄弟1的数据,传递给兄弟2
const handleData = (data) => {
sharedData.value = data
}
</script>
2.2 全局状态管理(复杂场景首选)
对于中大型应用,推荐使用 Pinia/Vuex 作为全局状态容器,兄弟组件通过读写全局状态实现通信,无需依赖父组件中转。Pinia 作为 Vue 3 官方推荐方案,语法简洁且天然支持 TypeScript:
// store/index.js
import { defineStore } from 'pinia'
export const useSharedStore = defineStore('shared', {
state: () => ({
brotherData: ''
}),
actions: {
updateData(data) {
this.brotherData = data
}
}
})
兄弟组件只需导入并操作全局状态即可:
<!-- 兄弟1组件 -->
<script setup>
import { useSharedStore } from '@/store'
const store = useSharedStore()
// 修改全局状态
const sendData = () => {
store.updateData('兄弟1的消息')
}
</script>
<!-- 兄弟2组件 -->
<script setup>
import { useSharedStore } from '@/store'
const store = useSharedStore()
// 读取全局状态(响应式)
console.log(store.brotherData)
</script>
2.3 事件总线(轻量临时方案)
Vue 2 中的 $bus 方式在 Vue 3 中不再适用,可通过第三方库 mitt 实现轻量级事件总线,适用于小型项目或临时通信需求:
npm install mitt
// utils/bus.js
import mitt from 'mitt'
export const bus = mitt()
<!-- 兄弟1组件 -->
<script setup>
import { bus } from '@/utils/bus'
const send = () => {
bus.emit('msg', '兄弟通信消息')
}
</script>
<!-- 兄弟2组件 -->
<script setup>
import { onMounted, onUnmounted } from 'vue'
import { bus } from '@/utils/bus'
onMounted(() => {
bus.on('msg', (data) => console.log(data))
})
// 组件卸载时取消监听,避免内存泄漏
onUnmounted(() => {
bus.off('msg')
})
</script>
该方式的缺点是事件名易冲突、数据流难以追踪,中大型项目不推荐作为核心通信方式。
3 跨层级 / 任意组件通信
跨层级组件(如爷孙组件)或无关联的任意组件通信,核心方案为 Provide/Inject 和全局状态管理。
3.1 Provide/Inject(依赖注入)
Vue 提供的依赖注入 API,父组件通过 provide 提供数据 / 方法,所有后代组件(无论层级多深)都可通过 inject 注入使用,解决了 “Props 透传” 问题(中间组件无需传递无关 Props)。
<!-- 顶层父组件 -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('light')
// 提供数据
provide('theme', theme)
// 提供修改数据的方法
provide('changeTheme', (newTheme) => {
theme.value = newTheme
})
</script>
<!-- 深层子组件 -->
<script setup>
import { inject } from 'vue'
// 注入数据和方法
const theme = inject('theme')
const changeTheme = inject('changeTheme')
// 调用方法修改顶层数据
const switchTheme = () => {
changeTheme('dark')
}
</script>
需注意:Provide/Inject 会使组件关系变得隐式,建议仅用于 “全局配置类数据”(如主题、语言)的传递,避免滥用导致数据流混乱。
3.2 全局状态管理(通用方案)
Pinia/Vuex 是跨层级、任意组件通信的通用方案,无论组件关系如何,都可通过读写全局状态实现通信,是中大型 Vue 应用的首选。其优势在于状态集中管理、修改可追踪,且支持调试工具可视化监控状态变化。
4 通信方式选型原则
| 组件关系 | 推荐方案 | 核心优势 |
|---|---|---|
| 父子组件 | Props + 自定义事件 | 数据流清晰、符合单向数据流 |
| 兄弟组件 | 父组件中转(简单)/ Pinia(复杂) | 简单场景低成本,复杂场景解耦 |
| 跨层级 / 任意组件 | Provide/Inject(轻量)/ Pinia(通用) | 避免 Props 透传,状态集中可控 |
核心原则总结:优先选择 “最小作用域” 的通信方式,数据尽量存储在 “需要使用该数据的最顶层组件” 中;避免直接修改子组件 / 注入的外部数据,通过事件 / 方法通知数据所有者修改,保证数据流可追溯。
- 父子组件通信是基础,核心依赖 Props(父传子)和自定义事件(子传父),ref + defineExpose 仅用于特殊场景;
- 兄弟组件通信优先用父组件中转或 Pinia,事件总线仅适用于轻量临时场景;
- 跨层级组件通信可选择 Provide/Inject(轻量)或 Pinia(通用),遵循单向数据流原则;
- 中大型项目建议以 Pinia 为核心管理全局共享数据,降低组件耦合度。
最后总结 🤔
-
Vue 3 Composition API:以 setup 为核心入口,通过 ref/reactive 实现响应式,解决复杂组件逻辑碎片化问题,支持逻辑复用,适配 TypeScript。
-
Vuex/Pinia:用于全局状态管理,Pinia 作为 Vue 3 官方推荐方案,简化语法、去掉 Mutation,轻量且类型支持更优,替代传统 Vuex。
-
Vue Router:实现 SPA 路由跳转,支持路由守卫、嵌套路由等功能,管控页面跳转规则与权限验证。
-
组件通信:按组件关系选型,父子组件用 Props + 自定义事件,兄弟 / 跨层级组件通过父组件中转、Pinia 或 Provide/Inject,遵循单向数据流原则。