面试 | Vue组件间有哪几种通信方式

605 阅读2分钟

前言

最近在刷面试题时,看见这个问题便做了个总结,欢迎各位补充!!!

1.props & $emit——适用于父子组件通信

父组件通过prop向子组件传递数据,子组件通过$emit触发事件给父组件传递数据。Vue基础,在此就不做过多的赘述。
这里提一下将一个对象的所有 property 都作为 prop 传入

// 父组件
post: {
  id: 1,
  title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>

// 等价于
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

2.ref——适用于父子组件通信

当ref作用在普通的 DOM 元素上使用,引用指向的就是 DOM 元素,如果用在组件上,引用就指向组件实例。进而我们能够获取到该组件的data,能够调取该组件的方法

注意

当指定的元素出现在v-for循环里时,此时返回的是一个数组,即使只循环一次,返回的也是一个数组。ref指向普通元素时,也是如此

<div v-for="item in ids" :key="item">
  <Child ref="child"></Child>
</div>

ids: [1, 2, 3]

this.$refs.child;  // [VueComponent, VueComponent, VueComponent]

3.$children——适用于父子组件通信

返回当前实例的直接子组件数组,

1.$children只返回直接子组件 2.$children 并不保证顺序,也不是响应式的。

用法和ref相似

<Child>
	<other></other>  
</Child>

this.$children; // [VueComponent]  不包含other

4.$parent——适用于父子组件通信

返回当前实例的父实例,如果有的话

// 情形一
// 父组件

<Child></Child>

// 子组件
this.$parent; // 返回的是父组件的实例

// 情形二
// 父组件
<Other>
  <Child></Child>
</Other>

// 子组件
this.$parent; // 此时返回的是Other组件的实例

5.$attrs——适用于隔代组件通信

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

// 父组件
<Child:abc="true" :flag="123"></Child>
// 子组件
props: ["flag"]
console.log(this.$attrs); // {abc: true}

// 传入内部组件
// 最外层组件
<Other :ids="ids"> </Other>
ids: [1, 2, 3]
// other组件
<Child v-bind="$attrs"></Child>
//  一旦在other组件的props中声明该属性,Child组件的$attrs将拿不到该ids属性
// props: ["ids"]
this.$attrs; // {ids: [1,2,3]}
// child组件
this.$attrs // {ids: [1,2,3]}

6.$root

访问当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

7.$listeners——适用于隔代组件通信

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

// 传入内部组件
// 最外层组件
<Other @click="handleChlick"> </Other>
methods: {
  handleChlick() {
    console.log("handleChlick");
  }
},
// other组件
<Child v-on="$listeners"></Child>
this.$listeners; // {click: ƒ}
this.$listeners.click() // handleChlick
// child组件
this.$listeners // {click: ƒ}
this.$listeners.click() // handleChlick

8.中央事件总线——适用于兄弟、父子、隔代组件

一个空的 Vue 实例作为中央事件总线(事件中心),然后通过bus.emit触发事件,bus.emit触发事件,bus.on监听触发的事件。从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

// bus.js
import Vue from "vue";
export default new Vue();
// 父组件
<Other></Other>
<Child></Child>
// other 组件
import bus from "./bus.js";
data() {
    return {
      age: 12
    };
  },
mounted() {
  //绑定全局事件globalEvent
  bus.$on("globalEvent", val => {
    this.age = val;
    console.log(this.age);
    // 销毁全局事件
    this.$once("hook:destroyed", () => {
      bus.$off("globalEvent");
    });
  });
}
// child 组件
<el-button @click="handleChlick">click</el-button>

import bus from "./bus.js";
methods: {
    handleChlick() {
      bus.$emit("globalEvent", 24);// 点击事件触发之后,other的age值被改变,打印24
    }
  },

9.provide & inject——适用于隔代组件通信

provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。

  • 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。provide选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持SymbolReflect.ownKeys的环境下可工作。inject选项应该是:
    • 一个字符串数组,或
    • 一个对象,对象的 key 是本地的绑定名,value 是:
      • 在可用的注入内容中搜索用的 key (字符串或 Symbol),或
      • 一个对象,该对象的:
        • from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
        • default property 是降级情况下使用的 value

提示:provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

// 父组件
<Child></Child>

provide: {
  foo: "test"
}

// child组件
<Other></Other>
inject: ["foo"],
mounted() {
  console.log(this.foo); // test
}
// other组件
inject: ["foo"],
mounted() {
  console.log(this.foo); // test
}

10.sync修饰符——适用于父子组件通信

Vue提供的一种缩写

// 父组件
<div class="wrapper">
  {{ isVisible }}
    <Child :isVisible.sync="isVisible"></Child>
		<el-button @click="isVisible = true">parentClick</el-button>
</div>
data() {
    return {
      isVisible: true
    };
  },
 
// 子组件  此处不必通过props声明,如果有其他方式要使用该属性时再声明
<el-button @click="$emit('update:isVisible', false)">childClick</el-button>

11. native修饰符——适用于父子组件

在一个组件的根元素上直接监听一个原生事件

// 父组件
<Child @click.native="handleClick"></Child>
 methods: {
    handleClick() {
      console.log("native!!");
    }
  },
 
// 子组件  点击根元素div所在的区域就会触发该事件
<div>
  ????
  <el-button>childClick</el-button>
</div>

12.slot

通过插槽内容访问子组件中才的内部数据

// 父组件
<Child>
  <template v-slot:default="{ user }">
		{{ user.firstName }}
  </template>
</Child>

// 子组件
<slot :user="user">
  {{ user.lastName }}
</slot>

user: {
  firstName: "胡",
  lastName: "歌"
}

注意 v-slot 只能添加在 template 上 (只有当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。),这一点和已经废弃的 slot attribute 不同。

13.observable——适用于兄弟、父子、隔代组件

在小型项目中用来代替Vuex

// store.js
import Vue from 'vue'

// 通过Vue.observable创建一个可响应的对象
export const store = Vue.observable({
  userInfo: {},
  roleIds: []
})

// 定义 mutations, 修改属性
export const mutations = {
  setUserInfo(userInfo) {
    store.userInfo = userInfo
  },
  setRoleIds(roleIds) {
    store.roleIds = roleIds
  }
}

<template>
  <div>
    {{ userInfo.name }}
  </div>
</template>
<script>
import { store, mutations } from '../store'
export default {
  computed: {
    userInfo() {
      return store.userInfo
    }
  },
  created() {
    mutations.setUserInfo({
      name: '子君'
    })
  }
}
</script>

14.Vuex