Vue3组件间通信

186 阅读1分钟

写在前面

将介绍的组件间通信方式:

  • props
  • attrattr、parent
  • provide、inject
  • $emit
  • Vuex

不是保姆级教程,仅提供一些简单的的应用实例和注释,阅读需要一定的vue基础

1. 父组件 ==>> 儿子组件

1.1props

@/components/Son.vue

<template>
    <h1>父组件传来一个数字{{aNumber}}</h1>
    <h1>父组件传来一个字符串{{aString}}</h1>
    <h1>父组件传来一个布尔值{{aBoolean}}</h1>
    <h1>>父组件传来一个对象{{anObject}}</h1>
    <h1>父组件传来一个对象的所有属性 name:{{name}} age:{{age}}</h1>
</template>

<script>
    export default {
        name:'Son',
        props:['aNumber','aString','aBoolean', 'anObject','name','age',],
        setup(props) {
            console.log(props)  
            //输出为:Proxy {aNumber: 1219, aString: 'hello', aBoolean: 'false', anObject: Proxy, name: 'jack', …}
            return {}
        }
    }
</script>
<style></style>

@/components/Father.vue

<template>
    <Son
        :aNumber="1219"
        :aString="'hello'"
        :aBoolean="'false'"
        :anObject="person"
        v-bind="person"
    />
</template>

<script>
    import {reactive} from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup(){
            let person = reactive({
                name:'jack',
                age:999
            })
            return {
                person
            }
        }
    }
</script>
<style></style>

1.2 $attr, $parent

@/components/Son.vue

<template>
</template>

<script>
    import {getCurrentInstance} from 'vue'
    export default {
        name:'Son',
        props:['aNumber'],
        setup(props, context){
            console.log(context.attrs) 
            //通过$attrs属性获得父组件已写入标签但子组件未用pros接收的属性
            //输出结果为 Proxy {person: Proxy, id: '999', __vInternal: 1}

            let myThis = getCurrentInstance()
            console.log(myThis.parent)
            //getCurrentInstance获得当前实例对象
            //myThis.parent是父组件的实例对象,实例对象拿到了,所有的数据就都能拿到
        }
    }
</script>
<style></style>

@/components/Father.vue

<template>
    <Son
        :aNumber="num"
        :person="person"
        id="999"
    />
</template>
<script>
    import {reactive, ref} from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup(){
            let num = ref(1219)
            let person = reactive({
                name:'green',
                age:888
            })
            return {
                num,
                person
            }
        }
    }
</script>
<style></style>

2. 父组件 ==>> 后代组件(儿子、孙子、曾孙子...),inject和provide

如下,父组件Father向孙组件GrandSon传值

@/components/Father.vue 父组件

<template>
    <h1>父组件:姓名--{{person.name}},年龄--{{person.age}}</h1>
    <button @click="ageGrow">fu年龄+1</button>
    <Son/>
</template>

<script>
    import Son from './Son.vue'
    import {provide, reactive} from 'vue';
    export default {
        name:'Father',
        components:{
            Son,
        },
        setup() {
            let person = reactive({
                name:'jack',
                age:250
            })//要传输的数据
            function ageGrow(){
                person.age++
            }//要传输的函数
            provide('person', person)   //传输数据
            provide('ageGrow', ageGrow) //传输函数
            return {
                person,
                ageGrow
            }
        }
    }
</script>
<style></style>

/components/Son.vue 儿子组件

<template>
    <GrandSon/>
</template>

<script>
    import GrandSon from './GrandSon.vue'
    export default {
        name:'Son',
        components:{GrandSon}
    }
</script>

@/components/GrandSon.vue 孙子组件

<template>
    <h1>孙子组件:姓名--{{person.name}},年龄--{{person.age}}</h1>
    <button @click="ageGrow">sun年龄+1</button>
</template>

<script>
    import { inject } from 'vue'
    export default {
        name:'Grandson',
        setup(){
            let person = inject('person')   //接收数据
            let ageGrow = inject('ageGrow') //接收函数
            return {
                person,
                ageGrow
            }
        }
    }
</script>
<style></style>

3. 儿子组件 ==>> 父组件 $emit

@/components/Father.vue

<template>
    <h1>子组件传来的数据:姓名--{{person.name}} 年龄--{{person.age}}</h1>
    <Son @myEmit="getPerson"/>
    <!-- 为子组件绑定自定义事件myEmit -->
</template>

<script>
    import { reactive } from 'vue'
    import Son from './Son.vue'
    export default {
        name:'Father',
        components:{Son},
        setup(){
            let person = reactive({})
            function getPerson(receivedPerson){
                Object.assign(person, receivedPerson)//Object.assign()非常实用的一个函数, 建议去mdn学一下
            }
            return {
                person,
                getPerson
            }
        }
    }
</script>
<style></style>

@/components/Son.vue

<template>
    <button @click="sendPerson">子组件向父组件传值</button>
    <button @click="person.age++">年龄+1</button>
    <!-- person.age++后,父组件显示数值并未变,可以得知当person变化后,emit并不会自动调用 -->
    <h1>子组件数据:姓名--{{person.name}} 年龄--{{person.age}}</h1>
</template>

<script>
    import { reactive } from 'vue'
    export default {
        name:'Son',
        setup(props, context){  //由于setup中没有this,所以需要通过上下文context来获得emit
            let person = reactive({
                name:'jack',
                age:987
            })
            function sendPerson(){
                context.emit('myEmit', person) //使用emit触发自定义事件
            }
            return {
                person,
                sendPerson
            }
        }
    }
</script>
<style></style>

其实所有父组件向子组件传值的方式都可以实现子组件向父组件传值,子组件向父组件传值的本质就是调用父组件提供的方法,然后把值通过实参的形式传给父组件,那么我们只要把父组件的方法传给子组件就可以实现子组件向父组件传值。

4. 任意组件 ==>> 任意组件

4.1 全局事件总线

new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	},
})

vue2采用上面的这种方式,通过在Vue原型对象中添加bus属性来实现事件总线。 由于 VueComponent.prototype.__proto__ === Vue.prototype ,所以在每个组件实例中都可以通过this.\bus的方式来访问到Vue.prototype.$bus对象,而这个对象本质就是Vue对象,自然有$on和$emit两个方法,某个组件通过$on方法来给这个$bus对象绑定事件,然后另外一个组件又通过$emit方法触发$bus对象上的事件,把数据通过参数传递进去,这样就实现了组件间的通信。 但是,vue3中这种方式已经不好用了,不能再往Vue原型对象上塞属性了,而是需要借助外部的库 tiny-emitter。除了把$bus换成emitter之外,实现事件总线的逻辑和方式上几乎和vue2如出一辙。 代码如下: @/emittter/index.js

import Emitter from 'tiny-emitter'
const emitter = new Emitter()
export default emitter

@/components/ComponentA.vue

<template>
    <h1>来自不知道哪个组件的信息:{{person}}</h1>
</template>

<script>
    import emitter from '@/emitter';
    import { reactive } from 'vue'
    export default {
        setup(){
            let person = reactive({})
            emitter.on('updatePerson', receviedPerson => { //为emitter实例绑定事件updatePerson
                Object.assign(person, receviedPerson)
            })
            return {person}
        }
    }
</script>
<style></style>

@/components/ComponentB.vue

<template>
    <button @click="sendPeson">向不知道哪个组件发送信息</button>
</template>

<script>
    import emitter from '@/emitter';
    export default {
        setup(){
            let person = {
                name:'jack',
                age: 996
            }
            function sendPeson(){
                console.log(2);
                emitter.emit('updatePerson', person)//触发updatePerson事件,参数为person对象
            }
            return {sendPeson}
        }
    }
</script>
<style></style>

tiny-emitter官方文档 Vue官方不建议使用事件总线进行组件间通信:

“在绝大多数情况下,不鼓励使用全局的事件总线在组件之间进行通信。虽然在短期内往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼”

4.2 Vuex

Vuex内容有点多,会新开一个博客专门写它。

暂时就学了这么多,待继续更新...