摘要
这是一篇对Vue组件间通信方式的总结笔记,包含了Vue2 及 Vue3 常用通信方式,之所以写下来是因为这既是对自身学习的一种复盘,同时也希望对有需要的朋友提供一些便利。
组件通信的基础模型
根据上面这幅图,可以把组件之间需要通信的方式分为三大类,分别是:
- 父子组件通信
- 兄弟组件通信
- 祖孙组件通信
组件通信方式详解
父子组件通信
Vue2 props / $emit ( $on )
父组件
<!-- template 模板 --> <cptx :val="123" @changeTxt="changeTxtFn">11</cptx>
// 父组件 methods: { changeTxtFn (data) { console.log('子组件传递过来的数据:', data) } },
子组件
<button @click="handleClick">向父组件传递数据</button>
export default { name: 'cptx', data () { return { bc: 11 } }, props: ['val'], mounted () { // 可以直接通过this.val获取到数据,模板中直接使用,例如:{{val}} console.log('this.val::', this.val) // 主要起到一个对事件的监听,可以获取触发事件后的回调参数 this.$on('changeTxt', (val) => { console.log('this.$on监听事件:', val) }) }, methods: { handleClick () { this.$emit('changeTxt', '这是子组件向父组件穿的数据123') } } }
子组件通过
props
接收父组件传递的参数:
子组件通过
$emit
向父组件传递数据,并通过$on
实现对事件的监听Vue3 defineProps 、defineEmits、defineExpose
父组件要访问子组件自身的定义的数据以及方法,直接通过Vue2中 ref 方式是获取不到的,需要通过defineExpose 暴露出去父组件
<!-- template 模板 --> <template> <PropsModal ref="abc" :propsNum="123" @changeParentPrice="handleChange">11</PropsModal> <button @click='handleClick'>点击</button> </template>
<script> import { defineComponent, ref } from 'vue' import PropsModal from '../components/propsModal.vue' export default defineComponent({ setup() { const abc = ref(null); // 父组件定义函数获取子组件通过 defineEmit 传递出来的参数 const handleChange = (data) => { console.log('data::', data) } // 父组件获取输出子组件通过 defineExpose 暴露出来的数据及函数 const handleClick = function () { console.log('refAbc::', abc) console.log('refAbc::', abc.value) console.log('refAbc::', abc.value.bc) console.log('refAbc::', abc.value.msg) const {person:{value:{name,age}}} = toRefs(abc.value._obj); console.log('refAbc::', toRefs(abc.value._obj)) console.log('refAbc::', toRefs(abc.value._obj).person.value) console.log('refAbc::', name,age) console.log('refAbc::', abc.value.btn) // 调用暴露出去的函数 abc.value.btn() } // 返回一个对象 return { handleChange, abc, handleClick } }, components: { PropsModal } }) </script>
子组件
<script setup> // 按需引入 import {defineProps, defineEmits, defineExpose, toRefs, toRef} from 'vue'; // 通过defineProps来接收父组件传递过来的参数 let props = defineProps({ // 方式一 // propsNum: String // 方式二 propsNum: { // type: String, // 设置参数类型方式一 type: [String, Number], // 设置参数类型方式二 default: '2', // 设置默认值 required: true // 是否为必须项,一般不予default同时使用 } }) // 如果在函数中使用,需要将响应式对象转换为普通对象ref, 如果是在模板中使用,则直接使用,例如{{propsNum}} const {propsNum} = toRefs(props); console.log('propsNum::', propsNum.value) // ----------------------------------------- 分隔符 ---------------------------------------------- /* 在子组件中使用 defineEmits 来声明 emits defineEmits函数参数是个数组,数组内容是自定义函数名称 不能定义在局部函数中,必须定义在全局中,否则会报语法错误,例如:Uncaught ReferenceError: defineEmits is not defined */ const emits = defineEmits(['changeParentPrice']) // 获取input事件下所输入的值 const changeCarName = (e) => { const currentVal = e.target.value; // emits函数第一个参数是自定义事件名称,第二个参数是需要传递的内容 emits('changeParentPrice', currentVal) } // ----------------------------------------- 分隔符 ---------------------------------------------- const bc = '1111111'; const msg = ref('子组件自定义数据'); const _obj = reactive({ person: { name: '张三', age: 18 } }); const btn = () => { console.log('打印输出msg', msg.value + '----') }; // 通过defineExpose暴露子组件需要暴露出去的数据及函数, 如果没有通过defineExpose暴露出去,父组件将不能获取子组件的数据及函数 defineExpose({ bc, msg, _obj, btn }) </script>
defineEmits
不能定义在局部函数中,必须定义在全局中,否则会报语法错误,例如:Uncaught ReferenceError: defineEmits is not defined
父组件
无法
获取通过ref
获取子组件暴露出来的数据及函数
父组件
可以
获取通过ref
获取子组件通过defineExpose
暴露出来的数据及函数$parent(子组件使用) / $children(父组件使用)
不太建议使用 this. p a r e n t 和 t h i s . parent 和 this. parent和this.children,因为会增加维护成本// 父组件 data () { return { aa: 112, changeText: '祖先组件通过provide传递给后代组件的参数' } }, mounted () { // 父组件通过 this.$children 访问子组件实例对象 console.log('父组件使用 this.$children:', this.$children) }, methods: { homeMethods () { console.log('父组件') }, changeNun () { this.changeText += '@ | ' console.log('this.changeText::', this.changeText) }, changeTxtFn (data) { console.log('子组件传递过来的数据:', data) } },
// 子组件 data () { return { bc: 11 } }, mounted () { // 子组件通过 this.$parent 访问父组件实例对象 console.log('子组件使用 this.$parent:', this.$parent) }, methods: { childMethods1 () { console.log('子组件1') }, childMethods2 () { console.log('子组件2') }, childMethods3 () { console.log('子组件3') }, handleClick () { this.$emit('changeTxt', '这是子组件向父组件穿的数据123') } },
this.$refs(父组件使用)
通过this.refs
可以获取子组件实例对象,可以访问子组件data中的数据以及方法<!-- template 模板 --> <cptx ref="aa" :val="123">11</cptx> <cptx ref="bb" :val="456">11</cptx>
// 父组件 mounted () { console.log('this.$refs:', this.$refs); } /* 输出结果: this.$refs: {aa: VueComponent, bb: VueComponent} aa: VueComponent {_uid: 4, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …} bb: VueComponent {_uid: 5, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …} [[Prototype]]: Object */ // 子组件
$attrs / $listeners
$attrs
用于多层次组件传递参数(组件标签的attribute,class和style除外),爷爷辈
组件向孙子辈
组件传递参数(注:参数不能被父辈prop识别,一旦被父辈prop识别且获取,则孙子辈组件不能获取到该参数)$listeners
用于多层次组件传递事件监听器,爷爷辈组件向父辈、孙子辈、曾孙子辈……组件传递事件(与 $attrs 不同,不存在半路被拦截的情况)【注意】表示在Vue3的虚拟DOM中,事件监听器现在只是以on为前缀的attribute, 这样它就成为了$attrs对象的一部分,因此
listeners被移除
了- 父组件
<!-- template 模板 --> <template> <div class="home-wrp"> <h1>{{ aa }}</h1> <cptx :val="123" :msg1="msg1" :msg2="msg2" @handleClick1="handleClick1">11</cptx> </div> </template>
export default { name: 'home', data () { return { aa: '这是祖先组件', msg1: '这是msg1的消息#####', msg2: '这是msg2的消息@@@@@', changeText: '祖先组件通过provide传递给后代组件的参数' } }, methods: { handleClick1(data) { console.log('执行祖先组件:', data) } }, }
子组件
作为父组件和孙子组件的传递中介(中间层),在儿子组件中给孙子组件添加v-bind="$attrs"
和v-on="$listeners"
,这样孙子组件才能接收到数据和事件。注意:在子组件中同样可以传递私有数据(bnc)以及事件,但是需要注意的是,单独传递的私有数据名称与祖先组件传递过来的数据对象里面的名称同名,那么将会把祖先组件传递进来的数据给覆盖掉,如果子组件传递的是同名的事件,却不会覆盖祖先组件传递的事件,但是会依次冒泡向上执行同名事件。
<!-- template 模板 --> <template> <div> <h1>{{ bc }}</h1> <!-- 此处有重点,子组件先拦截获取msg1 --> <h1>props传参:{{msg1}}</h1> <button @click="handleClick2">执行父组件</button> <!-- 此处有重点,v-bind 和 v-on 不能采用简写 :或者 @ --> <PageModal1 :bnc="180" :val="11111111111" v-bind="$attrs" v-on="$listeners" @handleClick1="handleClick4"></PageModal1> </div> </template>
<script> import PageModal1 from './pageModal1.vue' export default { name: 'cptx', data () { return { bc: '这是子组件' } }, props: ['msg1'], // 利用props接收父组件传递过来的参数 components: { PageModal1 }, methods: { handleClick2 (data) { console.log('执行父组件') this.$emit('handleClick1') }, handleClick4 () { console.log('执行父组件4444') } } } </script>
- 孙子组件
在孙子组件中只能使用props
接收从祖先组件传递过来的数据,一定要使传递的key保持同名。
例如在这个例子中祖先组件传递的 msg1, msg2, val
<!-- template 模板 --> <template> <div> <h1>{{ mm }}</h1> <h1>利用props获取祖先组件传递进来的val::{{val}}</h1> <h1 style="color: red">利用props获取祖先组件传递进来的msg1::{{msg1 || '获取不到msg1数据, 因为被子组件拦截先获取'}}</h1> <h1>利用props获取祖先组件传递进来的msg2::{{msg2}}</h1> <button @click="handleClick3">执行孙子组件</button> </div> </template>
<script> export default { name: 'pageModal1', data () { return { mm: '这是孙子组件' } }, props: ['val', 'msg2'], methods: { handleClick3 () { console.log('执行孙子组件') // 触发祖先组件传递的函数,并给到一个返回值'123' this.$emit('handleClick1', '123') } } } </script>
后面这张图看着可能有些乱,请各位同学结合代码一起看,另外上面这段示例是基于
Vue 2.5.2
版本。
兄弟组件通信
- vuex
- pinia
相关笔记一览 - eventBus
- $attrs / $listeners
主要通过消息订阅,发布的模式获取对应的数据
祖孙组件通信
兄弟组件同样适用于祖孙组件
provide / reject
vue2 中 provide 和 reject 的使用
注意:provide 和 inject 绑定并不是可响应的
。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。// 父组件 methods: { changeNun () { this.changeText += '@ | ' console.log('this.changeText::', this.changeText) } }, /* 写法1(正确写法) provide: { cd: '这是要传递的数据' }, */ /* 写法2(错误写法) provide: { cd: this.changeText }, */ // 写法2(正确写法) provide () { return { cd: this.changeText } }
// 后代组件 export default { name: 'pageModal1', data () { return {} }, inject: ['cd'], mounted () { console.log('this.cd::', this.cd) }, } /* 点击按钮后打印输出 home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | @ | home.vue?250d:44 this.changeText:: provide传递给后代组件的参数1@ | @ | @ | @ | @ | @ | @ | @ | @ | @ | */
vue3 中provide 和 reject 的使用
// 祖先组件 <script setup> // 引入函数 import {provide, reactive} from 'vue' // 定义响应式数据 let car = reactive({car: '奔驰',price: '50w'}); // 利用provide传递参数,params:为对象key, car:为对象key的值 provide('params', car) </script>
// 孙子组件(后代组件) <script> // 按需引入函数 import { defineComponent, inject, toRefs } from 'vue' // 采用另外一种实例化组件方式 export default defineComponent({ setup() { // 接收祖先组件传递的参数对象 let myParams = inject('params'); // 将响应式对象转为普通对象,这样就能够访问到传递的参数 const{car, price} = toRefs(myParams); console.log('测试provide/inject::', myParams,car.value, price.value) // 将解构的数据return回去,可以直接在模板中使用 return { car, price } }, }) </script>
关于Vue数据通信的方式就暂时总结到这里,后面还会保持更新。
如果本篇文章对各位同学有所帮助,欢迎大家点赞收藏
。
当然了,如果有不足之处,烦请各位同学能够指正,赵花花
将不胜感激。