组件通信 / 组件传值

365 阅读5分钟

什么是组件通信

  • 组件通过某种方式来传递信息以达到某个目的

组件通信解决了什么问题

  • 每个组件之间的都有独自的作用域,组件间的数据是无法共享的但实际开发工作中我们常常需要让组件之间共享数据,这也是组件通信的目的要让它们互相之间能进行通讯,这样才能构成完整系统

Vue2 组件通信方式

Vue2 组件通信的12中方法

  1. props
  2. $emit / v-on
  3. .sync
  4. v-model
  5. ref
  6. $children / $parent
  7. $attrs / $listeners
  8. provide / inject
  9. EventBus
  10. Vuex
  11. $root
  12. slot

父子组件通信可以用:

  • props
  • $emit / v-on
  • $attrs / $listeners
  • ref
  • .sync
  • v-model
  • $children / $parent

兄弟组件通信可以用:

  • EventBus
  • Vuex
  • $parent

跨层级组件通信可以用:

  • provide/inject
  • EventBus
  • Vuex
  • $attrs / $listeners
  • $root
1. props / $emit / v-on
  • 父组件A通过props的方式向子组件B传递,B 传 A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
2..sync
  • 可以帮我们实现父组件向子组件传递的数据 的双向绑定,所以子组件接收到数据后可以直接修改,并且会同时修改父组件的数据
3.v-model
  • 和 .sync 类似,可以实现将父组件传给子组件的数据为双向绑定,子组件通过 $emit 修改父组件的数据
4.ref
  • ref 如果在普通的DOM元素上,引用指向的就是该DOM元素;

  • 如果在子组件上,引用的指向就是子组件实例,然后父组件就可以通过 $refs.子组件名字 主动获取子组件的属性或者调用子组件的方法

5.$children / $parent
  • $children:获取到一个包含所有子组件(不包含孙子组件)的 VueComponent 对象数组,可以直接拿到子组件中所有数据和方法等
  • $parent:获取到一个父节点的 VueComponent 对象,同样包含父节点中所有数据和方法等
6.$attrs / $listeners
  • $attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合。通过 this.attrs获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过vbind="attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="attrs"
  • $listeners:包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on="$linteners"
7.provide / inject
  • provide / inject 为依赖注入,说是不推荐直接用于应用程序代码中,但是在一些插件或组件库里却是被常用,所以我觉得用也没啥,还挺好用的

  • provide:可以让我们指定想要提供给后代组件的数据或方法

  • inject:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接拿来用

  • 要注意的是 provide 和 inject 传递的数据不是响应式的,也就是说用 inject 接收来数据后,provide 里的数据改变了,后代组件中的数据不会改变,除非传入的就是一个可监听的对象

  • 所以建议还是传递一些常量或者方法

8.vuex 全局状态共享
  • Vuex 是状态管理器,集中式存储管理所有组件的状态。
9.$root
  • $root 可以拿到 App.vue 里的数据和方法
10.slot
  • Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置),作为承载分发内容的出口
  • 通过slot插槽向组件内部指定位置传递内容,完成该组件标签在不同场景的应用

默认插槽

  • 子组件用<slot>标签来确定渲染的位置,标签里面可以放DOM结构,当父组件使用的时候没有往插槽传入内容,标签内DOM结构就会显示在页面

  • 父组件在使用的时候,直接在子组件的标签内写入内容即可

具名插槽

  • 子组件用name属性来表示插槽的名字,不传为默认插槽

  • 父组件中在使用时在默认插槽的基础上加上slot属性,值为子组件插槽name属性值

作用域插槽

  • 子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上

  • 父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用

11.EventBus 中央事件总线
  • 不管是父子组件,兄弟组件,跨层级组件等都可以使用它完成通信操作

定义方法

  1. 抽离成一个单独的 js 文件 Bus.js , 然后在需要的地方引入

    • A / B=> import Bus from "./Bus.js"
    • A => Bus.$emit('自定义事件名', '传输的数据')
    • B => Bus.$on('自定义事件名', function(接收的数据 ) { } )
  2. 直接挂载到全局 Vue.prototype.$bus = new Vue() 使用的时候$bus.

    • A => this.$bus.$emit('自定义事件名', '传输的数据')
    • B => this.$bus.$on('自定义事件名', function(接收的数据 ) { } )
  3. 注入到 Vue 根对象上面 new Vue( { data : { Bus : new Vue() } )

Vue3 组件通信的8种方式

  • props
  • $emit
  • expose / ref
  • $attrs
  • v-model
  • provide / inject
  • Vuex
  • mitt

1. props

用 props 传数据给子组件方法

<child :msg2="msg2"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg2 = ref("这是传给子组件的信息2")
    // 或者复杂类型
    const msg2 = reactive(["这是传级子组件的信息2"])
</script>

// Child.vue 接收
<script setup>
    // 不需要引入 直接使用
    // import { defineProps } from "vue"
    const props = defineProps({
        // 写法一
        msg2: String
        // 写法二
        msg2:{
            type:String,
            default:""
        }
    })
    console.log(props) // { msg2:"这是传级子组件的信息2" }
</script>

2. $emit

// Child.vue 派发
<template>
    <button @click="emit('myClick')">按钮</buttom>
</template>
<script setup>
    const emit = defineEmits(["myClick","myClick2"])
</script>

// Parent.vue 响应
<template>
    <child @myClick="onMyClick"></child>
</template>
<script setup>
    import child from "./child.vue"
    const onMyClick = (msg) => {
        console.log(msg) // 这是父组件收到的信息
    }
</script>

3. expose / ref

父组件获取子组件的属性或者调用子组件方法

// Child.vue
<script setup>
    defineExpose({
        childName: "这是子组件的属性",
        someMethod(){
            console.log("这是子组件的方法")
        }
    })
</script>

// Parent.vue 
<template>
    <child ref="comp"></child>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    import child from "./child.vue"
    import { ref } from "vue"
    const comp = ref(null)
    const handlerClick = () => {
        console.log(comp.value.childName) // 获取子组件对外暴露的属性
        comp.value.someMethod() // 调用子组件对外暴露的方法
    }
</script>

4. attrs

attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合

// Parent.vue 传送
<child :msg1="msg1" :msg2="msg2" title="3333"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const msg1 = ref("1111")
    const msg2 = ref("2222")
</script>

// Child.vue 接收
<script setup>
    import { defineProps,  useAttrs } from "vue"
    const props = defineProps({
        msg1: String
    })
    const attrs = useAttrs()
    console.log(attrs) // { msg2:"2222", title: "3333" }
</script>

5. v-model

可以支持多个数据双向绑定

// Parent.vue
<child v-model:key="key" v-model:value="value"></child>
<script setup>
    import child from "./child.vue"
    import { ref, reactive } from "vue"
    const key = ref("1111")
    const value = ref("2222")
</script>

// Child.vue
<template>
    <button @click="handlerClick">按钮</button>
</template>
<script setup>
    import { defineEmits } from "vue"
    const emit = defineEmits(["key","value"])
    

    const handlerClick = () => {
        emit("update:key", "新的key")
        emit("update:value", "新的value")
    }
</script>

6. provide / inject

provide / inject 为依赖注入

provide:可以让我们指定想要提供给后代组件的数据或

inject:在任何后代组件中接收想要添加在这个组件上的数据,不管组件嵌套多深都可以直接拿来用

// Parent.vue
<script setup>
    import { provide } from "vue"
    provide("name", "沐华")
</script>

// Child.vue
<script setup>
    import { inject } from "vue"
    const name = inject("name")
    console.log(name) // 沐华
</script>

7. Vuex

// store/index.js
import { createStore } from "vuex"
export default createStore({
    state:{ count: 1 },
    getters:{
        getCount: state => state.count
    },
    mutations:{
        add(state){
            state.count++
        }
    }
})

// main.js
import { createApp } from "vue"
import App from "./App.vue"
import store from "./store"
createApp(App).use(store).mount("#app")

// Page.vue
// 方法一 直接使用
<template>
    <div>{{ $store.state.count }}</div>
    <button @click="$store.commit('add')">按钮</button>
</template>

// 方法二 获取
<script setup>
    import { useStore, computed } from "vuex"
    const store = useStore()
    console.log(store.state.count) // 1

    const count = computed(()=>store.state.count) // 响应式,会随着vuex数据改变而改变
    console.log(count) // 1 
</script>

8. mitt

Vue3 中没有了 EventBus 跨组件通信,但是现在有了一个替代的方案 mitt.js,原理还是 EventBus

先安装 npm i mitt -S

然后像以前封装 bus 一样,封装一下

mitt.js
import mitt from 'mitt'
const mitt = mitt()
export default mitt

然后两个组件之间通信的使用

// 组件 A
<script setup>
import mitt from './mitt'
const handleClick = () => {
    mitt.emit('handleChange')
}
</script>

// 组件 B 
<script setup>
import mitt from './mitt'
import { onUnmounted } from 'vue'
const someMethed = () => { ... }
mitt.on('handleChange',someMethed)
onUnmounted(()=>{
    mitt.off('handleChange',someMethed)
})
</script>

参考:juejin.cn/post/699968…

React组件间通信方式:

  • 1.父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯
  • 2.子组件向父组件通讯: props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中
  • 3.兄弟组件通信: 找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信
  • 4.跨层级通信: Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过
  • 5.布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入event模块进行通信
  • 6.局状态管理工具: 借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态