Code前端首页关于Code前端联系我们

Vue3做项目时兄弟组件怎么传参更灵活?附4种高频场景+全代码落地

terry 10小时前 阅读数 168 #Vue

最近做后台管理系统和电商弹窗模块的时候,总有人在群里问兄弟组件通信的问题:一会儿是购物车侧边栏和商品列表联动失效,一会儿是表单提交后要刷新旁边的列表但死活传不了参数,其实Vue3的兄弟组件通信方案并不少,但关键是得对应场景用,不然要么代码冗余,要么维护起来头疼,今天就结合我自己踩过的坑和常用的开发场景,把4种实用的方法都讲透,每种都有完整的可运行代码片段,看完直接就能套用到自己的项目里。

子组件触发父组件转发:适合简单的单向、双向数据更新

这种方法其实就是利用父组件做“中转站”,左边的兄弟组件 emit 事件给父组件,父组件拿到数据后要么直接改自己的响应式变量,要么把变量通过 props 传给右边的兄弟组件,为什么先讲这个?因为它不需要装额外的库,逻辑清晰,适合新手入门,也适合只有简单交互的小模块,比如商品列表勾选商品后,购物车侧边栏同步显示勾选数量这种场景。

举个电商项目里的真实小例子:左边是「商品筛选器」(子组件A),可以选价格区间;右边是「商品展示列表」(子组件B),要根据筛选结果刷新,父组件就是包裹这俩的「商品页容器」。

首先看父组件的代码:我们需要定义一个响应式的 priceRange 变量,用来存筛选器的选中值,然后把这个值通过 props 传给展示列表,再给筛选器绑定一个 update:priceRange 事件(这种命名是Vue3 v-model 默认支持的双向绑定写法,也可以自己起别的名字)。

<!-- 商品页容器父组件 Parent.vue -->
<template>
  <div class="product-page">
    <!-- 商品筛选器子组件A -->
    <ProductFilter v-model:priceRange="currentPriceRange" />
    <!-- 商品展示列表子组件B -->
    <ProductList :filterPrice="currentPriceRange" />
  </div>
</template>
<script setup>
import { ref } from 'vue'
import ProductFilter from './ProductFilter.vue'
import ProductList from './ProductList.vue'
// 定义响应式的价格区间变量
const currentPriceRange = ref({
  min: 0,
  max: 9999
})
</script>

然后是子组件A筛选器的代码:要接收 props 里的 priceRange,修改的时候不能直接改 props(Vue3 规范不能直接修改单向数据流的 props),得 emit 事件把新值传给父组件,这里用 v-model:priceRange 的话,emit 的事件名必须是 update:priceRange,参数就是新的区间值。

<!-- 商品筛选器子组件A ProductFilter.vue -->
<template>
  <div class="filter-box">
    <h3>价格筛选</h3>
    <div class="price-inputs">
      <input 
        type="number" 
        v-model.number="localMin" 
        placeholder="最低" 
        @input="updateRange"
      />
      <span>-</span>
      <input 
        type="number" 
        v-model.number="localMax" 
        placeholder="最高" 
        @input="updateRange"
      />
    </div>
  </div>
</template>
<script setup>
import { ref, watch } from 'vue'
// 定义props,接收父组件传来的priceRange
const props = defineProps({
  priceRange: {
    type: Object,
    required: true
  }
})
// 定义emit,用来触发父组件的更新
const emit = defineEmits(['update:priceRange'])
// 本地存一份筛选值,避免直接修改props
const localMin = ref(props.priceRange.min)
const localMax = ref(props.priceRange.max)
// 监听父组件传来的priceRange变化,同步本地值(比如父组件重置筛选时要用)
watch(() => props.priceRange, (newVal) => {
  localMin.value = newVal.min
  localMax.value = newVal.max
}, { deep: true })
// 修改本地值后,emit新值给父组件
const updateRange = () => {
  emit('update:priceRange', {
    min: localMin.value || 0,
    max: localMax.value || 9999
  })
}
</script>

子组件B展示列表的代码:直接接收 props 里的 filterPrice,然后用 computed 或者 watch 过滤数据就行,这里为了演示方便,我直接用了一个模拟的商品数组,实际开发中可以替换成 API 请求。

<!-- 商品展示列表子组件B ProductList.vue -->
<template>
  <div class="product-list">
    <h3>商品列表</h3>
    <ul>
      <li v-for="item in filteredProducts" :key="item.id">
        {{ item.name }} - ¥{{ item.price }}
      </li>
    </ul>
  </div>
</template>
<script setup>
import { computed } from 'vue'
// 定义props,接收筛选价格
const props = defineProps({
  filterPrice: {
    type: Object,
    required: true
  }
})
// 模拟的商品数组
const mockProducts = [
  { id: 1, name: '无线蓝牙耳机', price: 299 },
  { id: 2, name: '便携充电宝', price: 89 },
  { id: 3, name: '4K运动相机', price: 1999 },
  { id: 4, name: '机械键盘', price: 599 }
]
// 计算属性过滤商品,只要价格在min和max之间的
const filteredProducts = computed(() => {
  return mockProducts.filter(item => 
    item.price >= props.filterPrice.min && 
    item.price <= props.filterPrice.max
  )
})
</script>

这种方法的优点是简单、无依赖、符合Vue的单向数据流规范,适合小范围的兄弟组件交互;缺点是如果兄弟组件嵌套很深,或者有多个层级的兄弟组件需要通信,父组件就会变成“超级中转站”,代码会变得很乱,维护成本很高。

provide/inject:适合跨层级、复杂嵌套的兄弟/祖孙组件单向传参

刚才说的中转站方法,在嵌套超过2层的时候就不好用了,比如商品展示列表里还有一个「商品详情预览」的弹窗,弹窗里有一个「收藏商品」的按钮,收藏后要更新侧边栏的「我的收藏」数量——这时候从详情预览→商品列表→商品页→主应用→侧边栏,中转的层级太多了,provide/inject 就是用来解决这种“跨层级透传”问题的。

provide/inject 的工作原理有点像“依赖注入”:父组件或者祖先组件用 provide 提供一些数据或者方法,不管层级多深的子组件、孙组件、曾孙组件,都可以用 inject 注入这些数据或方法,但要注意,provide 默认是单向的,也就是说子组件不能直接修改 provide 提供的原始数据,最好是 provide 一个修改数据的方法,让子组件调用这个方法来更新,这样符合单向数据流的规范,也方便追踪数据的变化。

举个刚才说的收藏商品的例子:主应用是祖先组件,提供「我的收藏」的响应式数组和「添加收藏」的方法;侧边栏的「我的收藏」是子组件,注入数组显示数量;商品详情预览是嵌套3层的孙组件,注入添加收藏的方法,点击按钮时调用。

首先看主应用祖先组件的代码:这里用了 provide,为了让提供的数据是响应式的,必须用 ref 或者 reactive 包裹,provide 的第一个参数是“注入键”,可以是字符串,也可以是 Symbol(推荐用 Symbol,避免命名冲突),第二个参数是要提供的数据或方法。

<!-- 主应用祖先组件 App.vue -->
<template>
  <div class="app">
    <MySidebar />
    <ProductPage />
  </div>
</template>
<script setup>
import { ref, provide } from 'vue'
import MySidebar from './MySidebar.vue'
import ProductPage from './ProductPage.vue'
// 定义响应式的收藏数组
const myFavorites = ref([])
// 定义添加收藏的方法
const addToFavorites = (product) => {
  // 先检查是否已经收藏过,避免重复添加
  const isExist = myFavorites.value.some(item => item.id === product.id)
  if (!isExist) {
    myFavorites.value.push(product)
  }
}
// 用Symbol定义注入键,避免和其他组件的provide冲突
import { FAVORITES_KEY, ADD_FAVORITES_KEY } from './keys.js'
// 提供数据和方法
provide(FAVORITES_KEY, myFavorites)
provide(ADD_FAVORITES_KEY, addToFavorites)
</script>

然后是注入键的文件 keys.js,单独抽出来是为了避免在多个组件里重复写字符串,减少出错的概率:

// keys.js
export const FAVORITES_KEY = Symbol('favorites')
export const ADD_FAVORITES_KEY = Symbol('addFavorites')

接下来是侧边栏的「我的收藏」子组件:直接用 inject 注入 FAVORITES_KEY 对应的数组,显示长度就行。

<!-- 侧边栏子组件 MySidebar.vue -->
<template>
  <div class="sidebar">
    <h3>我的收藏</h3>
    <p>共 {{ favorites.length }} 件商品</p>
  </div>
</template>
<script setup>
import { inject } from 'vue'
// 引入注入键
import { FAVORITES_KEY } from './keys.js'
// 注入收藏数组,第二个参数是默认值(万一祖先组件没提供,就用空数组)
const favorites = inject(FAVORITES_KEY, [])
</script>

嵌套3层的商品详情预览孙组件:首先商品展示列表子组件里嵌套了预览按钮,点击按钮显示详情;然后详情预览里有收藏按钮,调用注入的 addToFavorites 方法。

先看商品展示列表的修改版 ProductList.vue:

<!-- 商品展示列表子组件 ProductList.vue -->
<template>
  <div class="product-list">
    <h3>商品列表</h3>
    <ul>
      <li v-for="item in filteredProducts" :key="item.id">
        {{ item.name }} - ¥{{ item.price }}
        <button @click="showPreview = true; currentPreviewProduct = item">
          查看详情
        </button>
      </li>
    </ul>
    <!-- 商品详情预览孙组件 -->
    <ProductPreview 
      v-if="showPreview" 
      :product="currentPreviewProduct" 
      @close="showPreview = false"
    />
  </div>
</template>
<script setup>
import { ref, computed } from 'vue'
import ProductPreview from './ProductPreview.vue'
// 保留之前的筛选逻辑
const props = defineProps({
  filterPrice: {
    type: Object,
    required: true
  }
})
const mockProducts = [/* 之前的模拟数据,这里省略重复写 */]
const filteredProducts = computed(() => {
  return mockProducts.filter(item => 
    item.price >= props.filterPrice.min && 
    item.price <= props.filterPrice.max
  )
})
// 新增预览相关的逻辑
const showPreview = ref(false)
const currentPreviewProduct = ref(null)
</script>

再看商品详情预览孙组件 ProductPreview.vue:

<!-- 商品详情预览孙组件 ProductPreview.vue -->
<template>
  <div class="preview-mask" @click.self="$emit('close')">
    <div class="preview-box">
      <button class="close-btn" @click="$emit('close')">×</button>
      <h3>{{ product.name }}</h3>
      <p>价格:¥{{ product.price }}</p>
      <p>描述:这是一款很棒的商品!</p>
      <button @click="handleAddFavorite">添加到收藏</button>
    </div>
  </div>
</template>
<script setup>
import { inject } from 'vue'
// 引入注入键
import { ADD_FAVORITES_KEY } from './keys.js'
// 定义props和emit
const props = defineProps({
  product: {
    type: Object,
    required: true
  }
})
const emit = defineEmits(['close'])
// 注入添加收藏的方法,默认值是一个空函数(避免报错)
const addToFavorites = inject(ADD_FAVORITES_KEY, () => {})
// 点击添加收藏的处理函数
const handleAddFavorite = () => {
  addToFavorites(props.product)
  // 可选:添加成功后给个提示
  alert('添加到收藏成功!')
}
</script>

这种方法的优点是不需要中转,跨层级也能轻松传参,代码结构清晰;缺点是数据的追踪不如 props/emit 直观,尤其是如果有多个地方提供了同一个注入键的话,很容易混淆,所以推荐用 Symbol 作为注入键,并且在 provide/inject 的地方加个注释说明一下用途,provide/inject 主要适合单向传参,如果需要双向的话,还是提供修改方法比较好。

mitt(事件总线库):适合任意组件之间的双向、多向通信

如果项目里有很多完全没有层级关系的组件需要通信,比如全局弹窗、全局通知、不同页面之间的状态同步(比如退出登录后清空所有购物车、收藏夹的数据),provide/inject 和中转站方法都不太合适,这时候就需要用到事件总线了。

Vue2 里的事件总线是直接用 new Vue() 实例来做的,因为 Vue2 的实例自带 $on、$emit、$off 这些事件方法;但 Vue3 把这些实例方法都移除了,所以我们需要装一个轻量级的事件总线库,最常用的就是 mitt,它的体积很小(压缩后只有200字节左右),API 也非常简单,和 Vue2 的事件总线用法差不多。

首先第一步是安装 mitt:在终端里输入 npm install mitt 或者 yarn add mitt 或者 pnpm add mitt 都可以。

然后第二步是创建一个事件总线的实例文件 bus.js,单独抽出来方便在所有组件里引用:

// bus.js
import mitt from 'mitt'
// 创建事件总线实例
const emitter = mitt()
// 导出实例
export default emitter

接下来举几个不同场景的例子:

场景1:全局通知——任意组件触发通知,通知组件显示

首先创建一个全局的通知组件 GlobalNotification.vue,放在 App.vue 里,它会监听 show-notification 事件,收到事件后显示对应的通知内容,3秒后自动消失。

<!-- 全局通知组件 GlobalNotification.vue -->
<template>
  <div v-if="show" class="notification" :class="type">
    {{ message }}
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import emitter from './bus.js'
// 响应式变量控制显示隐藏
const show = ref(false)
const message = ref('')
const type = ref('success') // success/error/warning/info
// 定义事件处理函数
const showNotification = (data) => {
  message.value = data.message || '操作成功'
  type.value = data.type || 'success'
  show.value = true
  // 3秒后自动隐藏
  setTimeout(() => {
    show.value = false
  }, 3000)
}
// 组件挂载时监听事件
onMounted(() => {
  emitter.on('show-notification', showNotification)
})
// 组件卸载时移除事件监听!!!这一步非常重要,否则会造成内存泄漏
onUnmounted(() => {
  emitter.off('show-notification', showNotification)
})
</script>
<style scoped>
.notification {
  position: fixed;
  top: 20px;
  right: 20px;
  padding: 12px 24px;
  border-radius: 4px;
  color: white;
  font-size: 14px;
  z-index: 9999;
}
.success { background-color: #67c23a; }
.error { background-color: #f56c6c; }
.warning { background-color: #e6a23c; }
.info { background-color: #409eff; }
</style>

然后把通知组件放在 App.vue 里:

<!-- App.vue -->
<template>
  <div class="app">
    <GlobalNotification />
    <!-- 其他组件 -->
    <ProductPage />
    <LoginPage />
  </div>
</template>
<script setup>
import GlobalNotification from './GlobalNotification.vue'
import ProductPage from './ProductPage.vue'
import LoginPage from './LoginPage.vue'
</script>

最后在任意组件里都可以触发通知,比如登录成功后:

<!-- 登录页面组件 LoginPage.vue -->
<template>
  <div class="login-page">
    <button @click="handleLogin">登录</button>
  </div>
</template>
<script setup>
import emitter from './bus.js'
const handleLogin = () => {
  // 模拟登录请求
  setTimeout(() => {
    // 登录成功后触发通知
    emitter.emit('show-notification', {
      message: '登录成功!欢迎回来',
      type: 'success'
    })
  }, 1000)
}
</script>

场景2:退出登录清空所有状态——多个组件监听同一个事件

比如有购物车组件、收藏夹组件、用户信息组件,都需要在退出登录时清空自己的状态,这时候可以在退出登录的按钮组件里触发 clear-all-state 事件,其他需要清空的组件都监听这个事件。

退出登录的按钮组件:

<!-- 退出登录按钮组件 LogoutButton.vue -->
<template>
  <button @click="handleLogout">退出登录</button>
</template>
<script setup>
import emitter from './bus.js'
const handleLogout = () => {
  // 模拟退出登录请求
  setTimeout(() => {
    // 触发清空所有状态的事件
    emitter.emit('clear-all-state')
    // 跳转到登录页(这里省略路由跳转的代码)
  }, 500)
}
</script>

购物车组件监听事件:

<!-- 购物车组件 MyCart.vue -->
<template>
  <div class="cart">
    <h3>购物车</h3>
    <p v-if="cartList.length === 0">购物车是空的</p>
    <ul v-else>
      <li v-for="item in cartList" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import emitter from './bus.js'
// 模拟购物车列表
const cartList = ref([
  { id: 1, name: '无线蓝牙耳机' },
  { id: 2, name: '便携充电宝' }
])
// 清空购物车的处理函数
const clearCart = () => {
  cartList.value = []
}
// 组件挂载时监听
onMounted(() => {
  emitter.on('clear-all-state', clearCart)
})
// 组件卸载时移除监听!!!
onUnmounted(() => {
  emitter.off('clear-all-state', clearCart)
})
</script>

这种方法的优点是灵活度最高,任意组件之间都能通信,不需要考虑层级关系;缺点是如果事件太多太乱,追踪数据的变化会非常困难,所以推荐用事件名前缀的方式来规范,notification/show、state/clear-all,并且在 bus.js 里加个注释列出所有的事件名和用途,方便维护,一定要记得在组件卸载时移除事件监听,否则会造成内存泄漏,这是很多新手容易忽略的坑。

Pinia/Vuex:适合全局共享状态的复杂交互

如果项目里需要共享的状态非常多,比如用户信息、购物车、收藏夹、全局配置、权限列表等,而且这些状态需要频繁修改、持久化存储(比如刷新页面后还要保留),那么前面三种方法都不太够用,这时候就需要用到状态管理库了。

Vue3 官方推荐的状态管理库是 Pinia,它是 Vuex 的升级版,API 更简洁,支持 TypeScript 更好,也没有 Vuex 那么多的概念(mutations 被取消了,直接在 actions 或者 state 的 mutations(Pinia里叫 actions 或者直接在 setup store 里修改)里修改就行),不过不管是 Pinia 还是 Vuex,原理都是一样的:把所有需要共享的状态放在一个全局的 store 里,所有组件都可以从 store 里读取状态,也可以调用 store 里的方法修改状态。

因为 Pinia 是官方推荐的,所以今天就以 Pinia 为例,讲一下怎么用它来做兄弟组件通信,首先第一步还是安装:npm install pinia 或者 yarn add pinia 或者 pnpm add pinia。

然后第二步是在 main.js 里注册 Pinia:

// main.js
import { createApp } from 'vue'
import App from './App.vue'
// 引入Pinia
import { createPinia } from 'pinia'
const app = createApp(App)
// 创建Pinia实例并注册到Vue应用
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

第三步是创建 store 文件,stores/cart.js,用来管理购物车的状态:

// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
// 用defineStore创建store,第一个参数是store的唯一ID,第二个参数可以是setup函数(推荐)或者options对象
export const useCartStore = defineStore('cart', () => {
  // 定义响应式的购物车列表(对应Vuex的state)
  const cartList = ref([
    { id: 1, name: '无线蓝牙耳机', price: 299, quantity: 1 },
    { id: 2, name: '便携充电宝', price: 89, quantity: 2 }
  ])
  // 定义计算属性:购物车总数量(对应Vuex的getters)
  const totalQuantity = computed(() => {
    return cartList.value.reduce((sum, item) => sum + item.quantity, 0)
  })
  // 定义计算属性:购物车总价格
  const totalPrice = computed(() => {
    return cartList.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  })
  // 定义添加商品到购物车的方法(对应Vuex的mutations和actions,这里因为没有异步操作,直接写就行;如果有异步操作,比如API请求,直接在函数里用async/await就行)
  const addToCart = (product) => {
    const existItem = cartList.value.find(item => item.id === product.id)
    if (existItem) {
      existItem.quantity++
    } else {
      cartList.value.push({ ...product, quantity: 1 })
    }
  }
  // 定义减少商品数量的方法
  const decreaseQuantity = (productId) => {
    const existItem = cartList.value.find(item => item.id === productId)
    if (existItem) {
      existItem.quantity--
      if (existItem.quantity === 0) {
        removeFromCart(productId)
      }
    }
  }
  // 定义删除商品的方法
  const removeFromCart = (productId) => {
    const index = cartList.value.findIndex(item => item.id === productId)
    if (index !== -1) {
      cartList.value.splice(index, 1)
    }
  }
  // 定义清空购物车的方法
  const clearCart = () => {
    cartList.value = []
  }
  // 返回所有需要暴露给组件的状态和方法
  return {
    cartList,
    totalQuantity,
    totalPrice,
    addToCart,
    decreaseQuantity,
    removeFromCart,
    clearCart
  }
})

第四步是在兄弟组件里使用 store:左边的商品列表组件调用 addToCart 方法添加商品,右边的购物车侧边栏组件读取 cartList、totalQuantity、totalPrice 显示,还可以调用 decreaseQuantity、removeFromCart、clearCart 方法修改。

左边的商品列表组件(简化版,去掉之前的筛选和预览):

<!-- 商品列表组件 ProductList.vue -->
<template>
  <div class="product-list">
    <h3>商品列表</h3>
    <ul>
      <li v-for="item in mockProducts" :key="item.id">
        {{ item.name }} - ¥{{ item.price }}
        <button @click="handleAddToCart(item)">加入购物车</button>
      </li>
    </ul>
  </div>
</template>
<script setup>
import { useCartStore } from './stores/cart.js'
// 模拟商品数组
const mockProducts = [
  { id: 1, name: '无线蓝牙耳机', price: 299 },
  { id: 2, name: '便携充电宝', price: 89 },
  { id: 3, name: '4K运动相机', price: 1999 },
  { id: 4, name: '机械键盘', price: 599 }
]
// 获取购物车store的实例
const cartStore = useCartStore()
// 点击加入购物车的处理函数
const handleAddToCart = (product) => {
  cartStore.addToCart(product)
}
</script>

右边的购物车侧边栏组件:

<!-- 购物车侧边栏组件 MySidebar.vue -->
<template>
  <div class="sidebar">
    <h3>购物车 ({{ cartStore.totalQuantity }})</h3>
    <p v-if="cartStore.cartList.length === 0">购物车是空的</p>
    <ul v-else>
      <li v-for="item in cartStore.cartList" :key="item.id">
        {{ item.name }} × {{ item.quantity }} = ¥{{ item.price * item.quantity }}
        <div class="quantity-controls">
          <button @click="cartStore.decreaseQuantity(item.id)">-</button>
          <span>{{ item.quantity }}</span>
          <button @click="cartStore.addToCart(item)">+</button>
          <button class="delete-btn" @click="cartStore.removeFromCart(item.id)">×</button>
        </div>
      </li>
    </ul>
    <div class="total-price" v-if="cartStore.cartList.length > 0">
      总计:¥{{ cartStore.totalPrice.toFixed(2) }}
    </div>
    <button class="clear-btn" v-if="cartStore.cartList.length > 0" @click="cartStore.clearCart">
      清空购物车
    </button>
  </div>
</template>
<script setup>
import { useCartStore } from './stores/cart.js'
// 获取购物车store的实例
const cartStore = useCartStore()
</script>
<style scoped>
.sidebar {
  width: 300px;
  padding: 20px;
  border-left: 1px solid #eee;
}
.quantity-controls {
  margin-top: 8px;
}
.quantity-controls button {
  margin: 0 4px;
}
.delete-btn {
  margin-left: 12px;
  color: red;
  border: none;
  background: none;
  cursor: pointer;
}
.total-price {
  margin-top: 16px;
  font-size: 16px;
  font-weight: bold;
}
.clear-btn {
  margin-top: 16px;
  padding: 8px 16px;
  background-color: #f56c6c;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}
</style>

最后把这两个组件放在 App.vue 里:

<!-- App.vue -->
<template>
  <div class="app" style="display: flex;">
    <ProductList style="flex: 1; padding: 20px;" />
    <MySidebar />
  </div>
</template>
<script setup>
import ProductList from './ProductList.vue'
import MySidebar from './MySidebar.vue'
</script>

这种方法的优点是状态统一管理,方便追踪数据的变化,支持持久化存储(只需要装一个 pinia-plugin-persistedstate 插件就行),适合大型项目;缺点是需要额外装库,学习成本比前面三种方法高一点,小型项目用的话可能有点“杀鸡用牛刀”。

总结一下怎么选

讲了这么多,可能有人还是不知道该选哪种方法,这里给大家一个简单的选择指南:

  1. 如果只有简单的单向、双向数据更新,兄弟组件嵌套不超过2层,选子组件触发父组件转发
  2. 如果兄弟组件嵌套很深,或者有跨层级的祖孙组件需要单向传参,选provide/inject
  3. 如果有很多完全没有层级关系的组件需要通信,比如全局通知、不同页面之间的简单状态同步,选mitt事件总线
  4. 如果项目里需要共享的状态非常多,而且需要频繁修改、持久化存储,选Pinia状态管理库

最后再提醒大家一句:不管用哪种方法,一定要符合Vue的开发规范,保持代码的清晰和可维护性,不要为了炫技而用复杂的方法,适合自己项目的才是最好的。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门