前言
组件通信分为:父子组件通信、隔代组件通信、兄弟组件通信。
props 和 $emit
适用于父子组件,这种方法是 Vue 组件的基础。
<!-- Parent.vue -->
<template>
<div>
<child :value="text" @input="handleValue"></child>
<h1>{{text}}</h1>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
data () {
return {
text: "父组件!"
}
},
components :{
Child
},
methods: {
handleValue (value) {
this.text = value;
}
}
}
</script>
<!-- Child.vue -->
<template>
<input type="text" @input="handleInput" :value="value">
</template>
<script>
export default {
props: ['value'],
methods: {
handleInput (e) {
this.$emit('input', e.target.value);
}
}
}
</script>
父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
子组件 $emit 触发当前实例上的事件,附加参数都会传给监听器回调。
ref 和 $parent / $children
适用父子组件通信
<!-- Parent.vue -->
<template>
<div>
<button @click="handleClick">+3</button>
<p>{{value}}</p>
<child ref="child"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
data () {
return {
value: 0
}
},
components: {
Child
},
methods: {
handleClick () {
this.$refs.child.changeNumber (this.value += 3)
},
changeValue (value) {
this.value = +value
}
}
}
</script>
通过 $refs 找到子组件,调用子组件中的函数。
<!-- Child.vue -->
<template>
<input type="text" :value="number" @input="handleInput">
</template>
<script>
export default {
data () {
return {
number: 0
}
},
methods: {
handleInput (e) {
let number = e.target.value
this.$parent.changeValue(number?number:0);
},
changeNumber (value) {
this.number = value
}
}
}
</script>
child 组件通过 $parent 找到父组件,调用父组件中的函数。
EventBus
适用于父子、隔代、兄弟组件通信。
全局 EventBus
// main.js
import Vue from 'vue'
Vue.prototype.$EventBus = new Vue()
在 Vue 原型新增 $EventBus 属性赋值一个新的 Vue 实例,用作事件总线。
<!-- Parent.vue -->
<template>
<div>
<child1></child1>
<child2></child2>
</div>
</template>
<script>
import Child1 from "./Child1.vue";
import Child2 from "./Child2.vue"
export default {
components: {
Child1,
Child2
}
}
</script>
<!-- Child1.vue -->
<template>
<input type="text" @input="handleInput" placeholder="请输入内容">
</template>
<script>
export default {
methods: {
handleInput (e) {
this.$EventBus.$emit("handleShow", e.target.value);
}
}
}
</script>
Child1 组件中利用 $emit 在事件总线上创建一个 handleShow 事件,并且传入参数 e.target.value。
<!-- Child2.vue -->
<template>
<p>{{text}}</p>
</template>
<script>
export default {
data () {
return {
text: ""
}
},
mounted () {
this.$EventBus.$on("handleShow", this.handleContent);
},
beforeDestroy() {
this.$EventBus.$off("handleShow", this.handleContent);
},
methods: {
handleContent (text) {
this.text = text;
}
}
}
</script>
Child2 组件中利用 $on 订阅事件总线的 handleShow 事件,触发相应函数。通过 $off 取消订阅 handleShow 事件。
局部 EventBus
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue();
创建一个 eventBus.js 文件,导出一个 vue 实例用作事件总线。
<!-- Child1.vue -->
<script>
import {EventBus} from "../eventBus.js";
export default {
methods: {
handleInput (e) {
EventBus.$emit("handleShow", e.target.value);
}
}
}
</script>
在所需的组件中导入事件总线。$emit 创建事件。
<!-- Child2.vue -->
<script>
import {EventBus} from "../eventBus.js";
export default {
data () {
return {
text: ""
}
},
mounted () {
EventBus.$on("handleShow", this.handleContent);
},
beforeDestroy() {
EventBus.$off("handleShow", this.handleContent);
},
methods: {
handleContent (text) {
this.text = text;
}
}
}
</script>
在所需的组件中导入事件总线。$on 订阅事件。
$attrs 和 $listeners
适用于隔代组件通信。
首先要明白 inheritAttrs 选项的作用:
- 默认为true,当父组件绑定参数没有被子组件的 props 接受,则会显示在子组件的根元素上。下图是子组件没有通过 props 接收 value2。

- false,则与上述情况相反,不会显示在根元素上,并且被子组件的 $attrs 属性所保存下来。
- 注意:这个选项不影响 class 和 style 绑定。Vue官方文档
<!-- Parent.vue -->
<template>
<div>
<child1
:value1="value1" :value2="value2"
@fun1="fun1" @fun2="fun2"></child1>
</div>
</template>
<script>
import Child1 from "./Child1.vue"
export default {
data () {
return {
value1: "父组件值1111",
value2: "父组件值2222"
}
},
components: {
Child1
},
methods: {
fun1 (value) { console.log(value); },
fun2 (value) { console.log(value); }
}
}
</script>
<!-- Child1.vue -->
<template>
<div>
<button @click="fun">点击触发父级事件fun1</button>
<p>{{value1}}</p>
<hr>
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from "./Child2.vue";
export default {
inheritAttrs: false,
props: ['value1'],
components: {
Child2
},
methods: {
fun () { this.$emit('fun1', "子组件参数"); }
}
}
</script>
v-bind="$attrs",使得 Child1 组件没有接收的数据传递给了 Child2 组件。
v-on="$listeners",使得 Child2 组件也可以通过 $emit 方法来调用父作用域中的v-on 事件监听器(不含 .native 修饰器) 。
<!-- Child2.vue -->
<template>
<div>
<button @click="fun">点击触发隔代组件事件</button>
<p>{{value2}}</p>
</div>
</template>
<script>
export default {
props: ['value2'],
methods: {
fun () { this.$emit('fun2', "隔代组件参数"); }
}
}
</script>
Child2 组件接收 value2 且调用隔代组件的事件。
Vuex
适用于父子、隔代、兄弟组件通信。
准备工作
- 安装 vuex
npm install vuex --save
- 在 main.js 文件引入 vuex。
import Vuex from 'vuex'
Vue.use(Vuex)
Vuex 核心
- state
state: {
count: 0
}
我的理解就是数据,整个应用中的共享数据。
- getter
getters: {
getCount: state => state.count,
// 例如
sum: state => state.count + 10
}
虽然可以直接通过 store.state.count 获取属性,但如果在获取数据之前要经过特殊处理,getter 无疑是非常有用的,上述代码 sum 给 count 加 10 再进行返回。
- mutation
mutation: {
addCountSync: state => state.count++
}
通过 commit 提交 mutation 修改 state。
“更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。”这句话是Vue官网上的原话。看到这句话相信很多人都会去尝试直接修改 state 来测试,然后发现这样也可以对 state 进行修改,但是在 Vue Devtools 调试工具的Vuex状态没有变更,这对于找错误和维护方面带来困难, 并且在开启严格模式下会报错。
一条重要的原则就是要记住 mutation 必须是同步函数。 为什么呢。
- action
actions: {
addCount ({commit}) {
setTimeout(() => commit('addCountSync') ,2000);
}
}
只有同步没有异步怎么行呢,毕竟大应用项目数据处理都会有异步操作。actions 可以包含异步操作,通过提交 mutation 来修改状态,而不是直接变更状态。上述代码就是设定定时器 2秒后提交 mutation 操作。
上述代码参数通过解构得到 commit 用于提交 mutation。
通过 dispatch 提交 action 操作。
合并上述代码尝试一个简单示例。
// main.js
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
getCount: state => state.count,
sum: state => state.count + 10
},
mutations: {
addCountSync: state => state.count++
},
actions: {
addCount ({commit}) {
setTimeout(() => commit('addCountSync') ,2000);
}
}
});
new Vue({
...
store
})
创建一个 store,创建 Vue 实例应用中加入这个 store,这样应用下的组件就能获取或修改 store 的 state。
<template>
<div>
<h1>{{count}}</h1>
<button @click="handleClickSync">同步改变count</button><br>
<button @click="handleClick">异步改变count</button>
</div>
</template>
<script>
export default {
computed: {
count () {
return this.$store.getters.getCount
}
},
methods: {
handleClickSync () {
this.$store.commit('addCountSync');
},
handleClick () {
this.$store.dispatch('addCount');
}
}
}
</script>
- module
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。这时把 store 划分模块,化繁为简。
Vuex 我简单记录了一小部分,完整地学习还是要移步去 Vue 官网。
provide 和 inject
适用于隔代通信。
在祖先组件中通过 provide 把数据注入。
<!-- Parent.vue -->
<template>
<div>
<h1>父级数据:</h1>
<p>{{person.name}}, {{age}}, {{city}}</p>
<button @click="handleClick">父级改变数据</button>
<hr>
<child></child>
</div>
</template>
<script>
import Child from "./Inject.vue"
export default {
name: "aaa",
components: {
Child
},
data () {
return {
person: {
name: "最初名字"
},
age: 10,
city: "广州"
}
},
provide () {
return {
person: this.person,
age: this.age,
getCity: () => this.city,
setCity: (value) => this.city = value
}
},
methods: {
handleClick () {
console.log("点击父级修改按钮!")
this.person.name = "父组件名字"
this.age = 20
this.city = "深圳"
}
},
created () {
console.log("父级数据:",this.person.name, this.age, this.city)
},
updated() {
console.log("更新后的父级数据:",this.person.name, this.age, this.city)
}
}
</script>
在子孙组件中通过 inject 提取所需要的数据。
<!-- Child.vue -->
<template>
<div>
<h1>子组件数据:</h1>
<p>{{person.name}}, {{age}}, {{city}}</p>
<button @click="handleClick">子组件修改数据</button>
</div>
</template>
<script>
export default {
name: "bbb",
inject: ['person', 'age', 'getCity','setCity'],
computed: {
city () {
return this.getCity()
}
},
methods: {
handleClick () {
console.log("点击子级修改按钮!")
this.person.name = "子组件名字"
this.age = 30
this.setCity('上海');
}
},
created () {
console.log("子级数据:",this.person.name, this.age, this.city)
},
updated() {
console.log("更新后的子级数据:", this.person.name, this.age, this.city)
}
}
</script>
控制台打印结果:
父级数据: 最初名字 10 广州
子级数据: 最初名字 10 广州
// 点击父级修改按钮
点击父级修改按钮!
更新后的子级数据: 父组件名字 10 深圳
更新后的父级数据: 父组件名字 20 深圳
// 点击子级修改按钮
点击子级修改按钮!
更新后的子级数据: 子组件名字 30 上海
更新后的父级数据: 子组件名字 20 上海
上述代码结果发现 age 没有响应,只是一开始把父组件 age 赋值给了子组件,而 person 这个对象和通过计算属性的 city 值能响应。
Vue官方的一句话:提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
vue 2.5.0+ 版本还可以为 inject 设置默认值使其变为可选项。Vue官方文档