
在实际的 Vue3 开发中,组件之间的数据传递与事件通信是最高频的需求之一。很多前端开发者经常面临这样的困惑:
this 不可用,旧的知识体系不再适用,如何优雅地实现通信?如果选错通信方式,轻则代码冗余、耦合度高,重则导致难以排查的 bug。本文将从实际场景出发,梳理 Vue3 中 4 种最主流的通信方式,并给出选型建议与对比。
原理说明
props 是父组件向子组件传递数据的最直接方式,方向是单向的(父 → 子),子组件不能直接修改 props。当子组件需要通知父组件时,通过 emit 触发自定义事件(子 → 父)。这是 Vue 中最基础、最常用的通信方式,适合组件层级较少、关系清晰的场景。
完整代码示例
父组件 Parent.vue:
<script setup>import { ref } from 'vue'import Child from './Child.vue'const message = ref('来自父组件的问候')const count = ref(0)function handleAdd() { count.value++}</script><template> <div class="parent"> <h3>父组件</h3> <!-- 通过 props 向下传递,通过 @event 监听子组件事件 --> <Child :message="message" :count="count" @add="handleAdd" /> </div></template><style scoped>.parent { border: 1px solid #409eff; padding: 16px;}</style>子组件 Child.vue:
<script setup>const props = defineProps({ message: { type: String, default: '' }, count: { type: Number, default: 0 }})const emit = defineEmits(['add'])</script><template> <div class="child"> <h4>子组件</h4> <p>接收到的 message:{{ message }}</p> <p>接收到的 count:{{ count }}</p> <button @click="emit('add')">触发 add 事件,让父组件 count++</button> </div></template><style scoped>.child { border: 1px solid #67c23a; padding: 12px;}</style>优缺点
原理说明
provide 允许祖先组件向下游的所有子孙组件提供数据,inject 让子孙组件注入这些数据,无论中间隔了多少层组件都无需逐层传递。它适合"爷爷 → 孙子"这类跨多层的场景,也常用于共享主题、配置等。
完整代码示例
祖先组件 GrandParent.vue:
<script setup>import { provide, ref } from 'vue'import Parent from './Parent.vue'const theme = ref('light')const user = ref({ name: '张三', role: 'admin' })function toggleTheme() { theme.value = theme.value === 'light' ? 'dark' : 'light'}provide('theme', theme)provide('user', user)provide('toggleTheme', toggleTheme)</script><template> <div class="grand-parent"> <h3>祖先组件</h3> <Parent /> </div></template>孙子组件 GrandChild.vue:
<script setup>import { inject } from 'vue'// 注入祖先提供的数据,第二个参数为默认值const theme = inject('theme', 'light')const user = inject('user')const toggleTheme = inject('toggleTheme')</script><template> <div class="grand-child"> <h5>孙子组件</h5> <p>注入的 theme:{{ theme }}</p> <p>注入的 user:{{ user.name }}({{ user.role }})</p> <button @click="toggleTheme">切换主题</button> </div></template>优缺点
reactive/ref 可以实现响应式共享。原理说明
Pinia 是 Vue 官方推荐的下一代状态管理库(Vuex 的继任者),基于 Composition API 设计,支持响应式、模块化、DevTools 调试,并天然适配 setup 语法糖。当多个不相关的组件需要共享同一份数据(如用户信息、购物车、主题)时,Pinia 是最佳选择。
完整代码示例
先安装:npm install pinia,然后在 main.js 中挂载:
// main.jsimport { createApp } from'vue'import { createPinia } from'pinia'importAppfrom'./App.vue'const app = createApp(App)app.use(createPinia())app.mount('#app')定义 store stores/user.js:
import { defineStore } from'pinia'exportconst useUserStore = defineStore('user', {state: () => ({name: '',token: '' }),getters: {isLogin: (state) => !!state.token },actions: {login(name, token) {this.name = namethis.token = token },logout() {this.name = ''this.token = '' } }})在任意组件中使用:
<script setup>import { storeToRefs } from 'pinia'import { useUserStore } from '../stores/user'const userStore = useUserStore()// 使用 storeToRefs 保持响应式,直接解构会丢失响应性const { name, token, isLogin } = storeToRefs(userStore)function doLogin() { userStore.login('李四', 'abc123')}</script><template> <div> <p v-if="isLogin">欢迎:{{ name }}</p> <button v-else @click="doLogin">登录</button> </div></template>优缺点
原理说明
通过给子组件绑定 ref,父组件可以直接拿到子组件的组件实例,进而访问其暴露的属性与方法。在 <script setup> 语法下,子组件默认不对外暴露内部内容,必须使用 defineExpose 显式声明要暴露的成员。
完整代码示例
子组件 Form.vue:
<script setup>import { ref } from 'vue'const username = ref('')const password = ref('')function validate() { return username.value && password.value}// 显式暴露给父组件defineExpose({ username, password, validate})</script><template> <div> <input v-model="username" placeholder="用户名" /> <input v-model="password" type="password" placeholder="密码" /> </div></template>父组件 Parent.vue:
<script setup>import { ref } from 'vue'import Form from './Form.vue'const formRef = ref(null)function submit() { if (formRef.value.validate()) { console.log('校验通过,用户名:', formRef.value.username) } else { alert('请填写完整信息') }}</script><template> <div> <Form ref="formRef" /> <button @click="submit">提交</button> </div></template>优缺点
选型建议:父子直接用 props/emit;跨多层用 provide/inject;全局共享用 pinia;需要命令式操控子组件时用 ref/defineExpose。
更多详细内容,请微信搜索"前端爱好者", 戳我 查看 。