组件之间通信, 主要存在于下面三种情况:
- 父子组件之间(Index-A、Index-B...)
- 兄弟组件之间(A-B)
- 隔代关系组件之间(Index-C, Index-D)
那么具体他们之间如何进行通信, 我们一一解答
首先先看下文件的目录结构
接下来看看具体的通信方式
1. props
& $emit
1.1 父传子 props
现在我们要从Index
页面给A
页面传递一个数组list
// index.vue
<template>
<div>
<A :list="list" />
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A
},
data() {
return {
list: ["html", "css", "js"],
};
},
},
</script>
// A.vue
<template>
<div>
<h2>Page A</h2>
<ul v-for="(item, index) in list" :key="index">
<li>{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "A",
props: ["list"],
};
</script>
效果图如下, 此时list
数组以自上而下的一种方式从Index
页面传递给A
组件, 且props
只可以从上一级想下一级传输, 即所谓的单向数据流
1.2 子传父 $emit
那A
组件想给Index
页面传送数据应该如何操作? 关于子组件给父组件传值, 一般都是通过一个事件搭配$emit
进行传输
// A.vue
<template>
<div>
<h2>Page A</h2>
<ul v-for="(item, index) in list" :key="index">
<!-- 定义事件 -->
<li @click="onItemClick(item)">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "A",
props: ["list"],
methods: {
onItemClick(item) {
this.$emit("on-item-click", `'${item}' from page A`);
},
},
};
</script>
父组件监听子组件上的事件名on-item-click
// index.vue
<template>
<div>
<!-- 监听 -->
<A :list="list" @on-item-click="handleItemClick" />
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
},
};
</script>
此时, 点击li
元素的时候, 我们就可以在Index
页面获取到A
页面传递过来的值
2. $children
& $parent
$parent
的类型为: Vue instance$children
的类型为: Array<Vue instance>, 需要注意$children
并不保证顺序,也不是响应式的
因为这两个API拿到的都是vue实例, 所以可以访问父组件或子组件身上变量, 方法等; 使用方式如下:
在A
页面中定义了一个变量msg
, 通过$parent
获取父组件中的数据
// A.vue
<template>
<div>
<h2>Page A</h2>
<p>{{ msg }}</p>
<ul v-for="(item, index) in $parent.list" :key="index">
<li @click="onItemClick(item)">{{ item }}</li>
</ul>
<hr />
</div>
</template>
<script>
export default {
name: "A",
data() {
return {};
},
// 获取Index中的list
mounted() {
console.log('A mounted', this.$parent.list);
},
};
</script>
现在我们在父组件中通过$children
获取到A
页面中的msg
并修改
// Index.vue
<template>
<div>
<A :list="list" @on-item-click="handleItemClick" />
<button @click="handleChange">change A's msg</button>
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
handleChange() {
console.log((this.$children[0].msg = "A's data is changed"));
},
},
};
</script>
3. ref
ref
如果用在普通的DOM元素上, 引用指向的就是DOM元素本身; 如果使用在组件上, 引用指向的就是该组件的实例
// index.vue
<template>
<div>
<A ref="componentA" :list="list" @on-item-click="handleItemClick" />
<p ref="p">p标签</p>
</div>
</template>
<script>
import A from "./components/A";
export default {
name: "Index",
components: {
A,
},
mounted() {
console.log("componentA ref", this.$refs.componentA);
console.log("p ref", this.$refs.p);
},
};
</script>
下面是打印的结果
当
ref
和v-for
一起使用的时候,$refs
得到的是一个数组
4. provide
& inject
-
provide:
Object | () => Object
-
inject:
Array<string> | { [key: string]: string | Symbol | Object }
这对选项需要一起使用, 允许一个祖先组件向所有后代注入一个依赖, 不论层级有多深, 并在其上下游关系成立的时间里始终生效
我们现在来看一下具体的例子: Index -> A -> C
先在Index
中提供数据
// index.vue
export default {
name: "Index",
components: {
A,
},
provide: {
name: "name from Index",
},
data() {
return {
list: ["html", "css", "js"],
};
},
methods: {
handleItemClick(value) {
console.log(`In page Index get ${value}`);
},
handleChange() {
console.log((this.$children[0].msg = "A's data is changed"));
},
},
};
然后在C
组件中通过inject
获取值
// C.vue
export default {
name: "C",
inject: {
value: "name",
data: {
from: "data1",
default: "1",
},
},
mounted() {
// Index 中并没有在 provide 中提供 data1 变量, 所以C组件会取默认值
console.log("C", this.value, this.data); // C name from Index 1
},
};
5. EventBus($emit, $on, $off)
这种方式是通过创建一个空的Vue
实例作为事件总线, 用它来触发事件, 监听事件, 解除事件, 从而实现任何组件之间的通信, 然后当项目逐渐扩大, 这种通信方式还是不建议选择, 难以维护
使用EventBus
来通信主要有以下几个步骤
5.1 创建一个事件总线并将其导出
// event-bus.js
import Vue from "vue";
export const EventBus = new Vue();
5.2 发送一个事件
主要有Index、A、B、C
几个页面
// index.vue
<template>
<div>
<A />
<B />
</div>
</template>
// A.vue
<template>
<div>
<h2 @click="handleClick">Page A</h2>
<C />
</div>
</template>
<script>
import C from "./C";
// 引入
import { EventBus } from "../event-bus.js";
export default {
name: "A",
components: {
C,
},
methods: {
// 触发事件
handleClick() {
EventBus.$emit("transfer-by-event-bus", "eventBus data");
},
},
};
</script>
5.3 监听事件, 接收数据
// B.vue
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
name: "B",
mounted() {
// 监听事件
EventBus.$on("transfer-by-event-bus", value => console.log("B", value)); // B eventBus data
},
};
</script>
// C.vue
<template>
<h5>C</h5>
</template>
<script>
import { EventBus } from "../event-bus";
export default {
name: "C",
mounted() {
EventBus.$on("transfer-by-event-bus", value => console.log("C", value)); // C eventBus data
},
};
</script>
5.4 移除事件
import { eventBus } from 'event-bus.js'
EventBus.$off('transfer-by-event-bus', {})
6. $attrs & $listeners
6.1 $attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (
class
和style
除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class
和style
除外),并且可以通过v-bind="$attrs"
传入内部组件——在创建高级别的组件时非常有用
我们先通过Index
页面向B
组件传递name, sex, age
三个值
// index.vue
<template>
<div>
<B :name="name" :sex="sex" :age="age" @on-click="handleClick" />
</div>
</template>
<script>
import B from "./components/B";
export default {
name: "Index",
components: {
B,
},
data() {
return {
name: "Lily",
sex: "female",
age: "20",
};
},
};
</script>
然后再B
组件中我们打印一下$attrs
, 看看能获取什么值? 可以看见$attrs
返回的是一个对象, 且键值对就是我们在Index
页面传给B
组件的值
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
export default {
name: "B",
mounted() {
console.log(this.$attrs); // {name: "Lily", sex: "female", age: "20"}
},
如果此时我们在B
组件的props
中接收一个变量, 看看有何变化? 打印出来的值不包含props
获取的name
字段
<template>
<div>
<h2>Page B</h2>
</div>
</template>
<script>
export default {
name: "B",
props: {
name: String,
},
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
现在需要把这几个变量再继续传给B
组件的子组件D
, 我们直接通过v-bind="$attrs"
即可
// B.vue
<template>
<div>
<h2>Page B</h2>
<D v-bind="$attrs" />
</div>
</template>
<script>
import D from "./D";
export default {
name: "B",
components: {
D,
},
props: {
name: String,
},
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
D
组件同样获取到了这几个值
// D.vue
<template>
<h5>D</h5>
</template>
<script>
export default {
name: "D",
mounted() {
console.log(this.$attrs); // {sex: "female", age: "20"}
},
};
</script>
6.2 $listeners
包含了父作用域中的 (不含
.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件——在创建更高层次的组件时非常有用
当子组件需要调用父组件中的方法, 我们就可以通过$listeners
来调用, 但前提是方法名必须在父组件中被定义
我们实操一下, 先在Index
页面的分别定义两个事件
<template>
<div>
<B
@click1="handeClick1"
@click2="handeClick2"
/>
</div>
</template>
<script>
import B from "./components/B";
export default {
name: "Index",
components: {
B,
},
data() {
return {};
},
methods: {
handeClick1() {
console.log("1");
},
handeClick2() {
console.log("2");
},
},
};
</script>
子组件B
调用方法如下, 现在我们分别点击one
, two
两个文本就可以分别打印出1
, 2
// B.vue
<template>
<div>
<h2 @click="$listeners.click1">one</h2>
<h2 @click="$listeners.click2">two</h2>
</div>
</template>
<script>
export default {
name: "B",
data() {
return {};
},
};
</script>
如果再向下传递给B
的子组件D
也是没有问题的, 我们只需要将$listeners
传下就可以了
// B.vue
<template>
<div>
<h2 @click="$listeners.click1">one</h2>
<h2 @click="$listeners.click2">two</h2>
<D v-on="$listeners" />
</div>
</template>
D
组件也像B
组件那样调用事件即可
// D.vue
<template>
<h5 @click="$listeners.click1">D</h5>
</template>
7. Vuex
Vuex
想必大家应该很熟悉, 它是一个专为Vue.js
应用程序开发的状态管理模式, 它让开发者能够聚焦于数据的更新而不是数据的传递
Vuex
主要有以下几个模块
-
state
: 用于数据的存储, 是store
中的唯一数据源 -
getters
: 同vue
中的计算属性, 基于state
数据进行二次包装来获取符合条件的数据 -
mutations
: 处理同步事件, 是唯一更改 store 中状态的方法 -
actions
: 可包含异步操作, 用于提交mutation
, 不可直接变更状态 -
modules
: 类似于命名空间, 用于项目中将各个模块的状态分开定义和操作, 便于维护
8. 总结
我们现在就开头说的三种场景再进行一次总结, 但是我们还是需要根据当下的场景选择合适的通信方式~
- 父子组件之间:
props & $emit
,$chilren & $parent
,ref
,EventBus
,provide & inject
,Vuex
- 兄弟组件之间:
Vuex
,EventBus
- 隔代关系组件之间:
EventBus
,$attrs & $listeners
,provide & inject
,Vuex