在 Vue 的组件化开发中,父子组件通信是绕不开的基础能力。原文围绕父组件向子组件传值、子组件向父组件反馈两条链路,归纳了六种方式:Props、自定义事件 $emit、v-model、Provide/Inject、ref 调用,以及 $attrs 与 $listeners 透传。六种方式并不是互相替代关系,选择时先看数据流向和组件耦合程度:单向数据流优先 Props,子组件反馈用 $emit,需要双向同步用 v-model,需要直接控制子组件实例用 ref,跨层级共享用 Provide/Inject,封装透传组件则考虑 $attrs 与 $listeners。原文还提示,$attrs 与 $listeners 在 Vue 3 Composition API 中主要对应 attrs 和 expose 的使用。
1. Props:最基础的单向传值
Props 是父子通信的主干。父组件可以把字符串常量静态传给子组件,也可以用 v-bind 缩写 : 绑定 data 或计算属性。静态写法如下:
- <!-- 父组件 Parent.vue -->
- <template>
- <ChildComponent title='静态标题' />
- </template>
复制代码
动态绑定写法:
- <!-- 父组件 Parent.vue -->
- <template>
- <ChildComponent :title='parentTitle' />
- </template>
- <script>
- export default {
- data() {
- return {
- parentTitle: '动态数据'
- }
- }
- }
- </script>
复制代码
这里的重点是父组件持有数据源,子组件通过 props 接收。原文将 Props 定位为单向数据流的主要方式,适合父级把配置、展示数据或初始值下发给子组件。
2. 自定义事件 $emit:子组件向父组件反馈
当子组件需要把点击结果或内部数据交给父组件时,用 $emit 触发自定义事件,父组件通过 v-on 或 @ 监听。子组件代码:
- <!-- 子组件 Child.vue -->
- <template>
- <button @click='handleClick'>点击触发事件</button>
- </template>
- <script>
- export default {
- methods: {
- handleClick() {
- this.$emit('custom-event', '从子组件传递的数据');
- }
- }
- }
- </script>
复制代码
父组件监听并接收数据:
- <!-- 父组件 Parent.vue -->
- <template>
- <ChildComponent @custom-event='handleData' />
- </template>
- <script>
- export default {
- methods: {
- handleData(data) {
- console.log('接收到的数据:', data);
- }
- }
- }
- </script>
复制代码
这段代码的关键是事件名要一致:子组件触发 custom-event,父组件就监听 custom-event。父组件的方法参数 data 就是 $emit 第二个参数传出的数据。
3. v-model:双向绑定的语法糖
v-model 常用于父子组件之间的双向数据绑定。原文指出,它的本质是子组件内部通过 props 接收 value,并通过 $emit('input') 更新父组件数据。默认用法下,父组件只写 v-model:
- <!-- 父组件 -->
- <template>
- <ChildComponent v-model='inputValue' />
- </template>
- <script>
- export default {
- data() {
- return { inputValue: '' };
- }
- }
- </script>
复制代码
如果需要自定义 v-model,子组件可以定义自己的 prop 和事件名称。原文示例中 prop 为 currentValue,事件为 update:currentValue:
- <!-- 子组件 Child.vue -->
- <script>
- export default {
- props: ['currentValue'],
- emits: ['update:currentValue'],
- methods: {
- change(val) {
- this.$emit('update:currentValue', val);
- }
- }
- }
- </script>
复制代码
自定义 v-model 时,要重点核对 prop 名与 update: 事件名是否对应。原文给出的模式是 currentValue 配 update:currentValue,调用 change 时把新值交给父组件,从而完成同步更新。
4. Provide/Inject:父子与跨层级共享
Provide/Inject 主要面向跨层级组件通信,但父子组件之间同样可用。父组件通过 provide 提供数据或函数,子组件通过 inject 获取。原文示例:
- <!-- 父组件 Parent.vue -->
- <script>
- export default {
- provide() {
- return {
- themeColor: 'blue',
- getUser: () => ({ name: 'Admin' })
- }
- }
- }
- </script>
复制代码
子组件注入:
- <!-- 子组件 Child.vue -->
- <script>
- export default {
- inject: ['themeColor', 'getUser']
- }
- </script>
复制代码
原文说明数据会响应式跟随变化。实际使用时,provide 返回的内容既可以是普通值,也可以是函数,示例中的 getUser 就是一个注入后可调用的函数。对于深层嵌套组件共享主题色或工具方法,这种方式比逐层传 props 更直接。
5. ref:父组件直接访问子组件实例
父组件可以通过 ref 标记子组件,然后在模板中访问子组件的属性和方法。原文示例:
- <!-- 父组件 Parent.vue -->
- <template>
- <ChildComponent ref='childRef' />
- <button @click='callChildMethod'>调用子组件方法</button>
- </template>
- <script>
- export default {
- methods: {
- callChildMethod() {
- this.$refs.childRef.childMethod();
- console.log(this.$refs.childRef.childData);
- }
- }
- }
- </script>
复制代码
这段代码中,this.$refs.childRef 指向子组件实例,因此可以调用 childMethod 并读取 childData。使用 ref 的前提是子组件已经渲染并建立了引用;排查时先确认 ref 名称与模板中的 ref 标记一致,再看调用发生在哪个生命周期或事件回调中。
6. $attrs 与 $listeners:属性和事件透传
原文将 $attrs 与 $listeners 列为第六种方式,主要用于组件属性透传和事件透传。它们适合封装中间层组件:父组件传下来的属性或监听事件,中间组件不逐项声明,而是继续传给内部组件。原文同时提示,在 Vue 3 Composition API 中主要是 attrs 和 expose 的使用。由于原文没有展开代码,这里不补写虚构示例,实际落地时应结合项目使用的 Vue 版本确认 API 形态。
选择与排查建议
从数据流看,Props 适合父到子的单向传递;$emit 适合子到父的事件反馈;v-model 适合需要双向同步的表单或值绑定;ref 适合父组件需要直接调用子组件方法、读取子组件数据的场景;Provide/Inject 适合跨层级共享;$attrs 与 $listeners 适合做透传封装。排查传值问题时,可以按顺序检查:数据方向是否选错;props 名称和绑定值是否正确;自定义事件名是否与监听名一致;自定义 v-model 的 prop 与 update: 事件是否配对;ref 是否指向已渲染的子组件实例;Provide/Inject 的注入键名是否与提供方一致。把这些通信方式按场景拆开使用,比在单个组件里堆叠多种传值手段更容易维护。 |