Vue 3 组件通信入门:props、emit 与 Pinia 一瞥
刚开始写 Vue 3 的时候,很多人会把所有逻辑塞进一个组件里,页面一复杂就变得难以维护。把页面拆成小组件之后,新的问题就来了:组件之间怎么说话?这篇文章把最常用的三种通信方式串起来讲清楚。
为什么组件要通信
组件化的本质是分工:列表组件负责展示,表单组件负责输入,按钮组件负责触发操作。数据不可能只待在一个组件里,父组件拿到的数据要交给子组件显示,子组件里的操作结果又要回传给父组件处理。如果没有清晰的通信规则,数据会在组件之间乱窜,出了问题很难定位。
父传子:props
父组件向子组件传数据用 props,这是最常用、也最推荐的方式。子组件先声明自己需要什么,父组件再把数据传进来,数据的来源一目了然。
<!-- 父组件 -->
<UserCard :name="userName" :age="18" />
<!-- 子组件 UserCard.vue -->
<script setup>
const props = defineProps({
name: String,
age: Number
})
</script>
<template>
<p>{{ props.name }},{{ props.age }} 岁</p>
</template>
用 props 有一个好处:子组件只管展示,不管数据从哪来,复用起来很方便。
子传父:emit
子组件想把消息传回父组件,就用 emit 触发自定义事件。父组件监听这个事件,拿到数据后再决定怎么处理。
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['update-name'])
function onInput(event) {
emit('update-name', event.target.value)
}
</script>
<template>
<input @input="onInput" placeholder="输入新名字" />
</template>
父组件里写 @update-name="userName = $event" 就能收到新值。事件名建议用短横线分隔的小写形式,和原生事件的写法保持一致,读起来不容易混淆。
兄弟组件怎么办
两个没有父子关系的组件要通信,最直接的办法是把共享的状态提升到它们共同的父组件:父组件持有数据,通过 props 分发给两个子组件,子组件再用 emit 把变化传回父组件。这样数据只有一条来路,流程很清晰。如果兄弟组件层级很深、中间要经过很多层 props,代码会变得啰嗦,这时可以考虑用全局状态。
Pinia 全局状态一瞥
Pinia 是 Vue 官方推荐的状态管理库,适合放那些很多组件都要用到的数据,比如登录用户信息、主题设置。用法很直观:先定义一个 store,再在需要的组件里读取或修改它。
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({ name: '小明' }),
actions: {
rename(newName) {
this.name = newName
}
}
})
组件里调用 useUserStore() 就能拿到同一份状态,不用一层层传 props。不过全局状态也不是越多越好,只有真正共享的数据才值得放进来。
常见坑
第一,props 是单向数据流,子组件不要直接修改 props 的值。需要改的时候,先把新值通过 emit 交给父组件,由父组件更新后再传下来,这样数据的变化才有迹可循。
第二,emit 的事件名要和监听的名字完全一致,大小写或短横线写错了,事件就悄悄失效了,页面上看不到任何报错,最容易浪费时间。
第三,不要一上来就把所有数据都放进 Pinia。只在当前组件内部用的数据,留在组件自己的 ref 里就够了,全局状态用得越多,组件之间的耦合就越强,后面改起来越费劲。
总结一下:父传子用 props,子传父用 emit,跨组件的共享数据再请 Pinia 出马。先把这三条路走顺,Vue 3 的组件通信就入门了。
- 点赞
- 收藏
- 关注作者
评论(0)