Vue 3跨组件传参之非父子层级通信解决方案

 更新时间:2025年11月03日 11:44:55   作者:Cherry Zack  
在Vue3组件开发中,组件之间的数据传递是一个非常重要的功能,这篇文章主要介绍了Vue 3跨组件传参之非父子层级通信解决方案,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

在现代前端开发中,组件化架构已经成为标准实践。Vue 3 作为当前最流行的前端框架之一,提供了多种优雅的跨组件通信方案。特别是当组件之间不存在直接的父子关系时,如何高效、可靠地传递数据就成为了每个 Vue 开发者必须掌握的技能

本文详细介绍 Vue 3中 5种常用的非父子组件传参方式,帮助你在不同场景下选择最合适的解决方案

一、Proviod / Inject - 官方推荐的跨层级方案

Proviod / Inject 是Vue 3 官方推荐的跨层级传参方式,特别适用于祖先组件向任意后代组件(不限层级深度)传递数据的场景

基本原理:这种方式基于依赖注入的设计模式

① Provide:祖先组件提供数据

② Inject:后代组件注入并使用数据

步骤1:祖先组件提供数据

<!-- ParentComponent.vue -->
<script setup>
import { provide, ref, reactive } from 'vue'

// 提供响应式数据
const user = ref({ 
  name: 'Alice', 
  age: 25,
  email: 'alice@example.com'
})

const theme = reactive({
  color: 'blue',
  fontSize: '16px'
})

// 使用provide提供数据
provide('user', user)
provide('theme', theme)
provide('appTitle', '我的应用') // 也可以提供非响应式数据
</script>

<template>
  <div>
    <h2>祖先组件</h2>
    <p>用户名:{{ user.name }}</p>
    <ChildComponent />
  </div>
</template>

步骤2:后代组件注入数据

<!-- GrandChildComponent.vue -->
<script setup>
import { inject } from 'vue'

// 注入数据
const user = inject('user')
const theme = inject('theme')
const appTitle = inject('appTitle')

// 可以直接修改响应式数据
const updateUserName = (newName) => {
  user.value.name = newName
}

const changeTheme = () => {
  theme.color = theme.color === 'blue' ? 'red' : 'blue'
}
</script>

<template>
  <div :style="{ color: theme.color, fontSize: theme.fontSize }">
    <h3>{{ appTitle }}</h3>
    <p>用户信息:{{ user.name }} ({{ user.age }}岁)</p>
    <button @click="updateUserName('Bob')">修改用户名</button>
    <button @click="changeTheme">切换主题</button>
  </div>
</template>

核心特点:

① 响应式传递:直接传递 ref 或 reactive 对象即可保持响应性

② 无层级限制:无论组件嵌套多深,都可以轻松获取数据

③ 类型安全:配合 TypeScript 可以实现完整的类型检查

二、Pinia - 现代化的全局状态管理

Pinia 是 Vue 3 官方推荐的状态管理库,它替代了 Vuex,提供了更简洁的 API 和更好的 TypeScript 支持

为什么选择 Pinia?

① 更简洁的 API 设计                        ② 更好的 TypeScript 集成

③ 完善的开发工具支持                     ④ 模块化的 store 设计

步骤1:安装Pinia

npm install pinia
# 或
yarn add pinia

步骤2:创建 Pinia 实例并注册

// main.js
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')

步骤3:定义 Store

// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  // 状态
  state: () => ({
    name: 'Alice',
    age: 25,
    email: 'alice@example.com',
    isLoggedIn: false
  }),
  
  // 计算属性
  getters: {
    fullInfo: (state) => `${state.name} (${state.age}岁)`,
    isAdult: (state) => state.age >= 18
  },
  
  // 方法
  actions: {
    updateName(newName) {
      this.name = newName
    },
    
    updateAge(newAge) {
      if (newAge >= 0) {
        this.age = newAge
      }
    },
    
    login(email, password) {
      // 模拟API调用
      return new Promise((resolve) => {
        setTimeout(() => {
          this.isLoggedIn = true
          this.email = email
          resolve(true)
        }, 1000)
      })
    },
    
    logout() {
      this.isLoggedIn = false
      this.email = ''
    }
  }
})

步骤4:在组件中使用 Store

<!-- UserProfile.vue -->
<script setup>
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'

// 获取store实例
const userStore = useUserStore()

// 使用storeToRefs保持响应性
const { name, age, fullInfo, isLoggedIn } = storeToRefs(userStore)

// 直接解构actions
const { updateName, updateAge, login, logout } = userStore

// 组件方法
const handleLogin = async () => {
  const success = await login('alice@example.com', 'password123')
  if (success) {
    console.log('登录成功')
  }
}
</script>

<template>
  <div class="user-profile">
    <h2>用户资料</h2>
    
    <div v-if="isLoggedIn">
      <p>欢迎,{{ fullInfo }}</p>
      <p>邮箱:{{ userStore.email }}</p>
      
      <div>
        <label>修改用户名:</label>
        <input v-model="name" />
        <button @click="updateName(name)">保存</button>
      </div>
      
      <div>
        <label>修改年龄:</label>
        <input v-model.number="age" type="number" />
        <button @click="updateAge(age)">保存</button>
      </div>
      
      <button @click="logout">退出登录</button>
    </div>
    
    <div v-else>
      <button @click="handleLogin">登录</button>
    </div>
  </div>
</template>

高级特性

模块化 Store

可以根据功能模块创建多个 store:

// stores/cart.js
export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [],
    total: 0
  }),
  actions: {
    addItem(product) {
      this.items.push(product)
      this.calculateTotal()
    },
    calculateTotal() {
      this.total = this.items.reduce((sum, item) => sum + item.price, 0)
    }
  }
})

Store 组合使用

<script setup>
import { useUserStore } from '@/stores/user'
import { useCartStore } from '@/stores/cart'

const userStore = useUserStore()
const cartStore = useCartStore()

const checkout = async () => {
  if (!userStore.isLoggedIn) {
    await userStore.login()
  }
  // 执行结账逻辑
  console.log('结账商品:', cartStore.items)
}
</script>

三、mitt - 轻量级事件总线

mitt 是一个轻量级的事件总线库,适用于简单的组件间通信场景

为什么选择 mitt?

① 体积小巧        ② API 简洁易用        ③ 良好的 TypeScript        ④ 高性能

步骤1:安装 mitt

npm install mitt
# 或
yarn add mitt

步骤2:创建事件总线实例

// utils/eventBus.js
import mitt from 'mitt'

// 创建事件总线实例
const eventBus = mitt()

export default eventBus

步骤3:发送事件

<!-- ComponentA.vue -->
<script setup>
import eventBus from '@/utils/eventBus'

const sendMessage = () => {
  // 发送事件
  eventBus.emit('message', {
    text: 'Hello from Component A',
    timestamp: new Date()
  })
}

const updateUser = () => {
  // 发送带参数的事件
  eventBus.emit('user:update', {
    name: 'New Name',
    age: 30
  })
}
</script>

<template>
  <div>
    <h3>组件A</h3>
    <button @click="sendMessage">发送消息</button>
    <button @click="updateUser">更新用户</button>
  </div>
</template>

步骤4:接收事件

<!-- ComponentB.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import eventBus from '@/utils/eventBus'

const messages = ref([])
const userName = ref('')

// 事件处理函数
const handleMessage = (data) => {
  messages.value.push(data)
}

const handleUserUpdate = (userData) => {
  userName.value = userData.name
}

// 组件挂载时注册事件监听
onMounted(() => {
  eventBus.on('message', handleMessage)
  eventBus.on('user:update', handleUserUpdate)
})

// 组件卸载时移除事件监听
onUnmounted(() => {
  eventBus.off('message', handleMessage)
  eventBus.off('user:update', handleUserUpdate)
})
</script>

<template>
  <div>
    <h3>组件B</h3>
    <p>当前用户:{{ userName }}</p>
    <div v-for="msg in messages" :key="msg.timestamp">
      <p>{{ msg.text }} - {{ msg.timestamp.toLocaleTimeString() }}</p>
    </div>
  </div>
</template>

高级用法:

事件命名规范

建议使用命名空间来组织事件

// 好的命名方式
eventBus.emit('user:created', userData)
eventBus.emit('user:updated', userData)
eventBus.emit('user:deleted', userId)

eventBus.emit('order:placed', orderData)
eventBus.emit('order:cancelled', orderId)

一次性事件

// 只监听一次事件
eventBus.once('message', (data) => {
  console.log('只执行一次:', data)
})

清楚所有事件

// 清除所有事件监听
eventBus.all.clear()

四、全局属性 - 工具类的全局共享

Vue 3 提供了app.config.globalProperties 来注册全局属性,适用于工具类和配置的全局共享

步骤1:注册全局属性

// main.js
import { createApp } from 'vue'
import axios from 'axios'
import App from './App.vue'

const app = createApp(App)

// 注册全局HTTP客户端
app.config.globalProperties.$http = axios.create({
  baseURL: 'https://api.example.com'
})

// 注册全局工具函数
app.config.globalProperties.$formatDate = (date) => {
  return new Intl.DateTimeFormat('zh-CN').format(date)
}

// 注册全局配置
app.config.globalProperties.$appConfig = {
  apiUrl: 'https://api.example.com',
  version: '1.0.0',
  theme: 'dark'
}

app.mount('#app')

步骤2:在组件中使用全局属性

<!-- AnyComponent.vue -->
<script setup>
import { getCurrentInstance } from 'vue'

// 获取当前组件实例
const instance = getCurrentInstance()

// 通过proxy访问全局属性
const $http = instance?.proxy?.$http
const $formatDate = instance?.proxy?.$formatDate
const $appConfig = instance?.proxy?.$appConfig

// 使用全局HTTP客户端
const fetchData = async () => {
  try {
    const response = await $http.get('/users')
    console.log('用户数据:', response.data)
  } catch (error) {
    console.error('请求失败:', error)
  }
}

// 使用全局工具函数
const formattedDate = $formatDate(new Date())

// 使用全局配置
console.log('API地址:', $appConfig.apiUrl)
console.log('应用版本:', $appConfig.version)
</script>

<template>
  <div>
    <h3>全局属性示例</h3>
    <p>当前时间:{{ formattedDate }}</p>
    <p>应用版本:{{ $appConfig.version }}</p>
    <button @click="fetchData">获取数据</button>
  </div>
</template>

TypeScript 支持

为了在 TypeScript 中获得类型提示,需要扩展 Vue 的类型定义:

// shims-vue.d.ts
import { AxiosInstance } from 'axios'

declare module 'vue' {
  interface ComponentCustomProperties {
    $http: AxiosInstance
    $formatDate: (date: Date) => string
    $appConfig: {
      apiUrl: string
      version: string
      theme: string
    }
  }
}

五、浏览器存储 - 持久化数据方案

浏览器提供的localStorage和sessionStorage是实现数据持久化的简单方案

localStorage vs sessionStorage

特性

localStorage

sessionStorage

生命周期

永久存储,除非手动清除

会话期间,页面关闭后清除

存储大小

约 5MB

约 5MB

作用域

同源所有页面共享

仅当前标签页

网络请求

会随 HTTP 请求发送到服务器

不会随 HTTP 请求发送

基础使用示例:

<!-- StorageComponent.vue -->
<script setup>
import { ref, watch } from 'vue'

// 用户设置
const userSettings = ref({
  theme: 'light',
  language: 'zh-CN',
  fontSize: '16px'
})

// 用户登录状态
const user = ref({
  isLoggedIn: false,
  name: '',
  token: ''
})

// 初始化:从localStorage读取数据
const initData = () => {
  try {
    // 读取用户设置
    const savedSettings = localStorage.getItem('userSettings')
    if (savedSettings) {
      userSettings.value = JSON.parse(savedSettings)
    }
    
    // 读取用户登录状态
    const savedUser = localStorage.getItem('user')
    if (savedUser) {
      user.value = JSON.parse(savedUser)
    }
  } catch (error) {
    console.error('读取存储数据失败:', error)
  }
}

// 保存到localStorage
const saveToLocalStorage = () => {
  try {
    localStorage.setItem('userSettings', JSON.stringify(userSettings.value))
    localStorage.setItem('user', JSON.stringify(user.value))
  } catch (error) {
    console.error('保存数据失败:', error)
  }
}

// 监听数据变化,自动保存
watch(userSettings, saveToLocalStorage, { deep: true })
watch(user, saveToLocalStorage, { deep: true })

// 登录方法
const login = (username, password) => {
  // 模拟登录API调用
  user.value = {
    isLoggedIn: true,
    name: username,
    token: 'fake-jwt-token'
  }
}

// 退出登录
const logout = () => {
  user.value = {
    isLoggedIn: false,
    name: '',
    token: ''
  }
  // 可选:清除特定存储
  localStorage.removeItem('user')
}

// 清除所有存储
const clearAllStorage = () => {
  localStorage.clear()
  sessionStorage.clear()
  // 重置状态
  userSettings.value = {
    theme: 'light',
    language: 'zh-CN',
    fontSize: '16px'
  }
  user.value = {
    isLoggedIn: false,
    name: '',
    token: ''
  }
}

// 初始化数据
initData()
</script>

<template>
  <div>
    <h3>浏览器存储示例</h3>
    
    <div v-if="user.isLoggedIn">
      <p>欢迎,{{ user.name }}!</p>
      <button @click="logout">退出登录</button>
    </div>
    
    <div v-else>
      <button @click="login('Alice', 'password')">登录</button>
    </div>
    
    <div>
      <h4>用户设置</h4>
      <div>
        <label>主题:</label>
        <select v-model="userSettings.theme">
          <option value="light">浅色</option>
          <option value="dark">深色</option>
        </select>
      </div>
      
      <div>
        <label>语言:</label>
        <select v-model="userSettings.language">
          <option value="zh-CN">中文</option>
          <option value="en-US">English</option>
        </select>
      </div>
    </div>
    
    <button @click="clearAllStorage" style="margin-top: 20px;">
      清除所有存储
    </button>
  </div>
</template>

封装存储工具类

// utils/storage.js
class StorageUtil {
  // localStorage操作
  static setLocal(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.error(`设置localStorage失败 (${key}):`, error)
    }
  }

  static getLocal(key, defaultValue = null) {
    try {
      const value = localStorage.getItem(key)
      return value ? JSON.parse(value) : defaultValue
    } catch (error) {
      console.error(`获取localStorage失败 (${key}):`, error)
      return defaultValue
    }
  }

  static removeLocal(key) {
    try {
      localStorage.removeItem(key)
    } catch (error) {
      console.error(`删除localStorage失败 (${key}):`, error)
    }
  }

  // sessionStorage操作
  static setSession(key, value) {
    try {
      sessionStorage.setItem(key, JSON.stringify(value))
    } catch (error) {
      console.error(`设置sessionStorage失败 (${key}):`, error)
    }
  }

  static getSession(key, defaultValue = null) {
    try {
      const value = sessionStorage.getItem(key)
      return value ? JSON.parse(value) : defaultValue
    } catch (error) {
      console.error(`获取sessionStorage失败 (${key}):`, error)
      return defaultValue
    }
  }

  static removeSession(key) {
    try {
      sessionStorage.removeItem(key)
    } catch (error) {
      console.error(`删除sessionStorage失败 (${key}):`, error)
    }
  }

  // 清除所有存储
  static clearAll() {
    try {
      localStorage.clear()
      sessionStorage.clear()
    } catch (error) {
      console.error('清除存储失败:', error)
    }
  }
}

export default StorageUtil

响应式封装:

<!-- ReactiveStorage.vue -->
<script setup>
import { ref, watch } from 'vue'
import StorageUtil from '@/utils/storage'

// 创建响应式存储
const createReactiveStorage = (key, defaultValue, useSession = false) => {
  const data = ref(StorageUtil[useSession ? 'getSession' : 'getLocal'](key, defaultValue))
  
  // 监听数据变化,自动保存
  watch(data, (newValue) => {
    StorageUtil[useSession ? 'setSession' : 'setLocal'](key, newValue)
  }, { deep: true })
  
  return data
}

// 使用响应式存储
const user = createReactiveStorage('user', {
  isLoggedIn: false,
  name: ''
})

const settings = createReactiveStorage('settings', {
  theme: 'light'
})

// 会话级存储
const tempData = createReactiveStorage('tempData', {}, true)
</script>

方案对比与选择指南

方式

适用场景

响应式

复杂度

推荐度

Provide/Inject

祖先→后代的跨层级传递

⭐⭐

⭐⭐⭐⭐

Pinia

任意组件的复杂状态共享

⭐⭐⭐

⭐⭐⭐⭐⭐

mitt 事件总线

简单的组件间通信

⭐⭐⭐

全局属性

工具类和配置的全局共享

⭐⭐

浏览器存储

数据持久化

⭐⭐⭐

总结 

到此这篇关于Vue 3跨组件传参之非父子层级通信解决方案的文章就介绍到这了,更多相关Vue3跨组件传参非父子层级通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue如何使用ElementUI对表单元素进行自定义校验及踩坑

    Vue如何使用ElementUI对表单元素进行自定义校验及踩坑

    有一些验证不是通过input select这样的受控组件来触发验证条件的 ,可以通过自定义验证的方法来触发,下面这篇文章主要给大家介绍了关于Vue如何使用ElementUI对表单元素进行自定义校验及踩坑的相关资料,需要的朋友可以参考下
    2023-02-02
  • vue3中vue.config.js配置Element-plus组件和Icon图标实现按需自动引入实例代码

    vue3中vue.config.js配置Element-plus组件和Icon图标实现按需自动引入实例代码

    这篇文章主要给大家介绍了关于vue3中vue.config.js配置Element-plus组件和Icon图标实现按需自动引入的相关资料,在Vue 3中可以通过配置vue.config.js文件来进行按需自动引入,需要的朋友可以参考下
    2024-02-02
  • 一文教你vue3 watch如何停止监视

    一文教你vue3 watch如何停止监视

    这篇文章主要为大家详细介绍了vue3中watch如何停止监视,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以跟随小编一起学习一下
    2024-12-12
  • element中form组件prop嵌套属性的问题解决

    element中form组件prop嵌套属性的问题解决

    本文主要介绍了element中form组件prop嵌套属性的问题解决,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • vue-router中query和params的区别解析

    vue-router中query和params的区别解析

    vue-router是Vue.js官方的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用,这篇文章主要介绍了vue-router中query和params的区别 ,需要的朋友可以参考下
    2022-10-10
  • vue3无法使用jsx的问题及解决

    vue3无法使用jsx的问题及解决

    这篇文章主要介绍了vue3无法使用jsx的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • vue+vuex+json-seiver实现数据展示+分页功能

    vue+vuex+json-seiver实现数据展示+分页功能

    这篇文章主要介绍了vue+vuex+json-seiver实现数据展示+分页功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-04-04
  • Vuex中actions的使用教程详解

    Vuex中actions的使用教程详解

    actions作为Vuex的五大核心之一,它的属性是用来处理异步方法的,通过提交mutations实现。本文将具体介绍一下actions的使用教程,需要的可以参考一下
    2022-01-01
  • vue3集成Element-Plus之全局导入和按需导入

    vue3集成Element-Plus之全局导入和按需导入

    这篇文章主要给大家介绍了关于vue3集成Element-Plus之全局导入和按需导入的相关资料,element-plus正是element-ui针对于vue3开发的一个UI组件库, 它的使用方式和很多其他的组件库是一样的,需要的朋友可以参考下
    2023-07-07
  • Vue Router 实现登录后跳转到之前想要访问的页面

    Vue Router 实现登录后跳转到之前想要访问的页面

    这篇文章主要介绍了Vue Router 实现登录后跳转到之前相要访问的页面,本文仅演示路由跳转和导航守卫相关代码的实现,不包含具体的权限验证和登录请求,需要的朋友可以参考下
    2022-12-12

最新评论