vue组件之间的通信分为两种:
父子组件之间的通信:
- props
- event(通过v-on在父组件声明,通过emit在子组件调用)
- v-model(prop是value,通过触发input事件动态更改变量)
- .sync 通过修饰符,默认绑定对象,并通过对象或者对象属性的name为事件名在子组件通过emit触发;
- ref
- $parent
- $children
非父子组件之间的通信:
- $attr
- $listeners
- provide,inject 动态注入
- $root 访问根实例
- vuex 集中式存储所有组件的状态
- $dispatch 和 $broadcast 通过组件树,事件冒泡的方法触发订阅的事件,主要用于父子孙组件之间的通信
- Event Bus Vue官网对 d i s p a t c h 和 dispatch和 dispatch和broadcast的升级替换方式,原文如下:
对于 $dispatch 和 $broadcast 最简单的升级方式就是:通过使用事件中心,允许组件自由交流,无论组件处于组件树的哪一层。由于 Vue 实例实现了一个事件分发接口,你可以通过实例化一个空的 Vue 实例来实现这个目的。
vue 组件间的通信是 vue 开发中很基础也十分重要的部分,作为使用 vue 的开发者每天都在使用。同时,vue 通信也是面试中非常高频的问题,有很多面试题,都是围绕通信展开。
本文会介绍常见的通信方式,并分析每种方式的使用场景和注意点。
vue中提倡单向数据流,这是为了保证数据流向的简洁性,使程序更易于理解。但对于一些边界情况,vue也提供了隐性的通信方式,这些通信方式会打破单向数据流的原则,应该谨慎使用。
下面我们将组件通信分为父子组件通信 和 非父子组件通信进行分析
父子组件通信
prop 和 events
通过prop传递给子组件,通过emit触发父组件定义的event;这是最基础的父子组件之间的通信方式;
注意的几个点:
1.不要在子组件修改props,这样会破坏单向的数据绑定,使文档流难以理解。可以通过在子组件设置data
属性接收或computer
方法转换;
2. 如果 props 传递的是引用类型(对象或者数组),在子组件中改变这个对象或数组,父组件的状态会也会做相应的更新,利用这一点就能够实现父子组件数据的“双向绑定”,虽然这样实现能够节省代码,但会牺牲数据流向的简洁性,令人难以理解,最好不要这样去做。想要实现父子组件的数据“双向绑定”,可以使用 v-model
或 .sync
。
v-model 指令
v-model
是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind
和 v-on
的语法糖,在一个组件上使用 v-model,默认会为组件绑定名为value
的 prop 和名为 input
的事件。
当我们组件中的某一个 prop 需要实现上面所说的”双向绑定“时,v-model 就能大显身手了。有了它,就不需要自己手动在组件上绑定监听当前实例上的自定义事件,会使代码更简洁。
注意一点:一些输入类型比如单选框和复选框按钮可能想使用 value
prop 来达到不同的目的。使用 model
选项可以回避这些情况产生的冲突。
model选项设置在子组件
上,代码如下:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
// this allows using the `value` prop for a different purpose
value: String,
// use `checked` as the prop which take the place of `value`
checked: {
type: Number,
default: 0
}
},
// ...
})
<my-checkbox v-model="foo" :value="value"></my-checkbox>
相当于
<my-checkbox :checked="foo" v-on:change="val=>{foo = val};" :value="value"></my-checkbox>
.sync 修饰符
.sync
修饰符在 vue 1.x 的版本中就已经提供,1.x 版本中,当子组件改变了一个带有.sync
的 prop 的值时,会将这个值同步到父组件中的值。这样使用起来十分方便,但问题也十分明显,这样破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
于是乎,在vue 2.0中移除了 .sync
。但是在实际的应用中,.sync
是有它的应用场景的,所以在 vue 2.3 版本中,又迎来了全新的 .sync
。
新的 .sync 修饰符所实现的已经不再是真正的双向绑定,它的本质和 v-model
类似,只是一种缩写
。
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
上面的代码,使用 .sync
就可以写成
<text-document v-bind:title.sync="doc.title"></text-document>
使用方法:
this.$emit('update:title', newTitle)
v-model 和 .sync 对比
.sync
从功能上看和v-model
十分相似,都是为了实现数据的“双向绑定”,本质上,也都不是真正的双向绑定,而是语法糖
。相比较之下,.sync 更加灵活,它可以给多个 prop 使用,而 v-model 在一个组件中只能有一个。
从语义上来看,v-model 绑定的值是指这个组件的绑定值,比如 input 组件,select 组件,日期时间选择组件,颜色选择器组件,这些组件所绑定的值使用 v-model 比较合适。其他情况,没有这种语义,个人认为使用 .sync 更好。
Ref
ref 特性可以为子组件赋予一个 ID 引用,通过这个 ID 引用可以直接访问这个子组件的实例。当父组件中需要主动获取子组件中的数据或者方法时,可以使用 $ref 来获取。
使用 ref
时,有两点需要注意
$refs
是作为渲染结果被创建的,所以在初始渲染的时候它还不存在,此时无法无法访问。$refs
不是响应式的,只能拿到获取它的那一刻子组件实例的状态,所以要避免在模板和计算属性中使用它
非父子组件通信
$attrs 和 $listeners
当要传递的数据很多时,就需要在中间的每个组件都重复写很多遍,反过来从后代组件向祖先组件使用 events 传递也会有同样的问题。使用 $attrs 和 $listeners 就可以简化这样的写法。
$attrs
会包含父组件中没有被 prop 接收的所有属性(不包含class 和 style 属性),可以通过 v-bind="$attrs" 直接将这些属性传入内部组件。
$listeners
会包含所有父组件中的 v-on 事件监听器 (不包含 .native 修饰器的) ,可以通过 v-on="$listeners" 传入内部组件。
provide 和 inject
provide
和 inject
需要在一起使用,它可以使一个祖先组件向其所有子孙后代注入一个依赖,可以指定想要提供给后代组件的数据/方法,不论组件层次有多深,都能够使用。
<!--祖先组件-->
<script>
export default {
provide: {
author: 'yushihu',
},
data() {},
}
</script>
<!--子孙组件-->
<script>
export default {
inject: ['author'],
created() {
console.log('author', this.author) // => yushihu
},
}
</script>
dispatch 和 broadcast
vue 在2.0版本就已经移除了$dispatch
和$broadcast
,因为这种基于组件树结构的事件流方式会在组件结构扩展的过程中会变得越来越难维护。但在某些不使用 vuex 的情况下,仍然有使用它们的场景。所以element ui
和 iview
等开源组件库中对 broadcast
和 dispatch
方法进行了重写,并通过 mixin
的方式植入到每个组件中。
Vue源码分析 ----- 消失的$dispatch 和 $broadcast
eventBus
eventBus
是Vue官方给出的对于dispatch
和broadcast
的优化方案:
对于 $dispatch 和 $broadcast 最简单的升级方式就是:通过使用事件中心,允许组件自由交流,无论组件处于组件树的哪一层。由于 Vue 实例实现了一个事件分发接口,你可以通过实例化一个空的 Vue 实例来实现这个目的。
可以通过单独的事件中心管理组件间的通信:
// 将在各处使用该事件中心
// 组件通过它来通信
var eventHub = new Vue()
在各个组件使用,可以使用 $emit
,$on
,$off
分别来分发、监听、取消监听事件:
<!--组件A-->
<script>
import Bus from 'eventBus.js';
export default {
methods: {
sayHello() {
Bus.$emit('sayHello', 'hello');
}
}
}
</script>
<!--组件B-->
<script>
import Bus from 'eventBus.js';
export default {
created() {
Bus.$on('sayHello', target => {
console.log(target); // => 'hello'
});
}
}
</script>
通过 $root 访问根实例
通过 $root,任何组件都可以获取当前组件树的根 Vue 实例,通过维护根实例上的 data,就可以实现组件间的数据共享。
通过这种方式,虽然可以实现通信,但在应用的任何部分,任何时间发生的任何数据变化,都不会留下变更的记录,这对于稍复杂的应用来说,调试是致命的,不建议在实际应用中使用。
Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。
使用 Vuex 并不代表就要把所有的状态放入 Vuex 管理,这样做会让代码变的冗长,无法直观的看出要做什么。对于严格属于组件私有的状态还是应该在组件内部管理更好。
对于小型的项目,通信十分简单,这时使用 Vuex
反而会显得冗余和繁琐,这种情况最好不要使用 Vuex
,可以自己在项目中实现简单的 Store
。
//store.js
var store = {
debug: true,
state: {
author: 'yushihu!'
},
setAuthorAction (newValue) {
if (this.debug) console.log('setAuthorAction triggered with', newValue)
this.state.author = newValue
},
deleteAuthorAction () {
if (this.debug) console.log('deleteAuthorAction triggered')
this.state.author = ''
}
}
和 Vuex 一样,store 中state
的改变都由 store 内部的 action
来触发,并且能够通过log
保留触发的痕迹。这种方式十分适合在不需要使用Vuex
的小项目中应用。
与$root
访问根实例的方法相比,这种集中式状态管理的方式能够在调试过程中,通过 log 记录来确定当前变化是如何触发的,更容易定位问题。