面试必问:Vue的12种组件通讯方法

133 阅读3分钟

一、12种方法汇总

  • 1.通过属性传值prop
  • 2.修饰符 .sync
  • 3.v-model
  • 4.通过 ref 注册子组件引用
  • 5.通过$parent获取父组件实例的方法或者属性
  • 6.$children
  • 7.通过事件传值 $emit
  • 8.Vuex
  • 9.eventBus
  • 10.provide / inject
  • 11.通过 $root 访问根实例
  • 12. attrs与listenter

1.prop

父组件向子组件传送数据最常用的方法

// 父组件 List.vue
<template>
  <div>
    <List-item :str="str" :obj="obj" :arr="arr"></List-item>
  </div>
</template>
<script>
import ListItem from "./ListItem";
export default {
  data() {
    return {
      str: "给子组件传值",
      obj: {msg: "给子组件传值"},
      arr: [1, 2, 3]
    }
  },
  components: {
    ListItem
  }
}
</script>

// 子组件 ListItem.vue
<template>
  <div>
    <div>{{msg}}</div>
    <div>{{obj}}</div>
    <div>{{arr}}</div>
  </div>
</template>
<script>
export default {
  props: {
    msg: String, // props是字符串
    obj: Object, // props是对象
    arr: Array   // props是数组
  }
}
</script>

2、3. v-model和 .async

v-model 和 .async 的通信方式和区别可以看我另一篇文章详解 juejin.cn/post/710648…

4.ref

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,可以通过 ref 特性为这个子组件赋予一个 ID 引用

<template>
  <div>
    <List-item ref="item" :title="title"></List-item>
    <div>{{data}}</div>
  </div>
</template>
<script>
import ListItem from "./List-item";
export default {
  data() {
    return {
      title: "我是title",
      data: ""
    }
  },
  components: {
    ListItem
  },
  mounted() {
    this.data = this.$refs.item.message;
  }
}
</script>

5.$parent

这种方式,从严格意思上讲不是值的传递,而是一种"取"(不推荐直接通过实例进行值的获取)。

可以通过 Vue 的实例属性 $parent 获得父组件的实例,借助实例可以调用父实例中的方法,或者获取父实例上的属性,从而达到取值的目的。

// 父组件 List.vue
...
<script>
export default {
  data() {
    return {
      message: "hello children",
      msg: "hello"
    }
  },
  methods: {
    sendMessage() {
      return this.message;
    }
  }
}
</script>

// 子组件 ListItem.vue
<template>
  <div>
    <div>{{data}}</div>
    <div>{{msg}}</div>
  </div>
</template>
<script>
export default {
  data() {
    return {
      data: "",
      msg: ""
    }
  },
  mounted() {
    this.data = this.$parent.sendMessage(); // 调用父实例中的方法
    this.msg = this.$parent.msg; // 获取父实例中的属性
  }
}
</script>

  • 通过 $parent 获取父实例 this.$parent.event
  • 通过 props 传递方法。
  • 通过 $emit 监听父组件中的方法 this.$emit("event")

6.$children

获取父组件下的所有子组件的实例,返回的是一个数组 使用范围:该属性只针对vue组件,与js中childNodes还是有区别的。
$ildren: 获取子组件实例集合
hildNodes: 获取子节点集合
使用方法:

<template>
    <A></A>
    <B></B>
</template>
<script>
    export default{
        data(){},
        mounted(){
            //  通过$children可以获取到A和B两个子组件的实例
            console.log('children:',this.$children)
        }
    }
</script>
其中,

this.$children[0]
可以获取到A组件的实例,一样的,我们可以使用A组件的属性以及他的方法。

7.$emit

子组件使用 $emit 发送一个自定义事件,事件名称是一个字符串。
父组件使用指令 v-on 绑定子组件发送的自定义事件。

// 父组件 List.vue
<template>
  <div>
    <!-- 监听自定义事件 -->
    <List-item v-on:welcome="getWelcome"></List-item>
  </div>
</template>
<script>
import ListItem from "./List-item";
export default {
  components: {
    ListItem
  },
  methods: {
    getWelcome(data) {
      alert(data)
    }
  }
}
</script>

// 子组件 ListItem.vue
<template>
  <button @click="handleClick">Click me</button>
</template>
<script>
export default {
  methods: {
    handleClick() {
   // 使用 $emit 发送自定义事件 welcome
      this.$emit('welcome', 'hello');
    }
  }
}
</script>

8.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>

9.eventBus

eventBus够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题

// 方法一
// 抽离成一个单独的 js 文件 Bus.js ,然后在需要的地方引入
// Bus.js
import Vue from "vue"
export default new Vue()

// 方法二 直接挂载到全局
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()

// 方法三 注入到 Vue 根对象上
// main.js
import Vue from "vue"
new Vue({
    el:"#app",
    data:{
        Bus: new Vue()
    }
})

// 在需要向外部发送自定义事件的组件内
<template>
    <button @click="handlerClick">按钮</button>
</template>
import Bus from "./Bus.js"
export default{
    methods:{
        handlerClick(){
            // 自定义事件名 sendMsg
            Bus.$emit("sendMsg", "这是要向外部发送的数据")
        }
    }
}

// 在需要接收外部事件的组件内
import Bus from "./Bus.js"
export default{
    mounted(){
        // 监听事件的触发
        Bus.$on("sendMsg", data => {
            console.log("这是接收到的数据:", data)
        })
    },
    beforeDestroy(){
        // 取消监听
        Bus.$off("sendMsg")
    }
}

10. 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>

11.$root

通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享

//main.js 根实例
new Vue({
    el: '#app',
    store,
    router,
    // 根实例的 data 属性,维护通用的数据
    data: function () {
        return {
            author: ''
        }
    },
    components: { App },
    template: '<App/>',
});
 
 
<!--组件A-->
<script>
export default {
    created() {
        this.$root.author = '于是乎'
    }
}
</script>
 
 
<!--组件B-->
<template>
    <div><span>本文作者</span>{{ $root.author }}</div>
</template>

注意:通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。

12. attrs 与attrs与listenter

多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时

$attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合。通过this.attrs获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v−bind = " attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="attrs获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v−bind="attrs"
$listeners:包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on=“$linteners”
使用方式是相同的:

// Parent.vue
<template>
    <child :name="name" title="1111" ></child>
</template
export default{
    data(){
        return {
            name:"小解"
        }
    }
}

// Child.vue
<template>
    // 继续传给孙子组件
    <sun-child v-bind="$attrs"></sun-child>
</template>
export default{
    props:["name"], // 这里可以接收,也可以不接收
    mounted(){
        // 如果props接收了name 就是 { title:1111 },否则就是{ name:"小解", title:1111 }
        console.log(this.$attrs)
    }
}

常见使用场景可以分为三类:

  • 父子组件通信: props$parent / $children provide / inject 、 ref \ $refs 、 $attrs / $listeners
  • 兄弟组件通信: eventBus 、 vuex、 自己实现简单的 Store 模式
  • 跨级通信: eventBus、 Vuex、 自己实现简单的 Store 模式、 provide / inject 、 $attrs / $listeners

参考文章