在组合各类vue组件的时候,为了保证数据传递的一致性,我们需要这些通信机制。
一、props/$emit/$on
使用场景
通常用于“父子”通信。
时序图
优点
- 可追溯:父组件调用时,会声明有哪些“属性”传给孩子;会声明有哪些“方法”可以让孩子上报父级。
如果你编写正确的话,可以让代码“清晰明了”,方便追溯两边的传递。
缺点
只能传递一级,当需要“祖师爷 -> 爷爷 -> 父亲 -> 孩子”,使用起来不太方便。
示例
这是我们最常用的属性,也最为清楚;
子组件通过 props 属性【接收】父组件传来的属性
props: {
// 优惠券、优惠码
detailType: {
type: String,
default: 'ticket',
},
// 查看、复制、编辑、新建
enterType: {
type: String,
default: 'add',
},
// 复制、编辑、查看的依据
pkId: {
type: [String, Number],
default: '',
},
},
子组件通过 $emit 属性【发送】改变的信息给父组件
this.$emit('update-callback')
父组件也存在关于子组件的表达
<ForbidReceiveDialog ref="forbidReceiveDom"
v-if="forbidReceiveVisible"
:enterType="'just test'"
@update-callback="reload">
</ForbidReceiveDialog>
可以看到,父组件的引用,显示地体现了子组件有哪些:【被传递的值】【可像父级上报的函数】
二、$attrs/$listeners
有时候我们想从 爷爷传递给孙子,如果通过 props 来传递的话,可能就需要多余的传递。
使用场景
【跨级传递】爷爷组件,想要传值给孙子组件,父亲节点用不上这些属性。
因为父亲节点用不上,多写一次没必要
时序图
优点
让代码更简洁。(如果通过 props 的方式实现,需要多传一些实际上用不到的值)
缺点
不容易追溯。(除Vuex的解决方案,由于传递层级过深,都有这个问题)
示例
爷爷组件A
<attrA @testL="testL" :a="a" :b="b" :c="c" :d="d"></attrA>
父亲组件B
<attrAB v-bind="$attrs" v-on="$listeners"></attrAB>
孙子组件C
{{$attrs.a || '该属性没有'}}
{{$attrs.b || '该属性没有'}}
{{$attrs.c || '该属性没有'}}
{{$attrs.d || '该属性没有'}}
<button @click="triggerListeners">触发$listeners</button>
triggerListeners() {
this.$emit('testL')
}
三、provide/inject
provide
和inject
主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。-
使用场景
时序图
优点
可追溯。通过 inject 来声明,不会覆盖子级本身的变量。
-
缺点
不容易追溯。(除Vuex的解决方案,由于传递层级过深,都有这个问题)
示例一、普通的传递
父级——provide提供【“属性”、“方法”】
provide: {
global: 'I\'m global.',
provideKey: 'balbalaj',
fn: () => {
console.log('hhh')
}
},
子孙级——inject进来的【“属性”、“方法”】
{{selfName}}
<button @click="fn">provide提供函数,inject进来</button>
inject: {
selfName: {
from: 'global',
default: 'xxXx'
},
fn: 'fn'
},
示例二、用 provide/inject 实现页面的reload
父级——template
<main-content v-if="!$store.state.common.contentIsNeedRefresh" />
父级——provide提供
provide () {
return {
// 刷新
refresh () {
this.$store.commit('common/updateContentIsNeedRefresh', true)
this.$nextTick(() => {
this.$store.commit('common/updateContentIsNeedRefresh', false)
})
}
}
},
子级——inject引入
inject: ['refresh'],
四、$refs/$parent/$children
使用场景
$refs有其单独的使用场景,并不针对于通信传值。
时序图
优点
示例
<attrA ref="targetDom"></attrA>
<button @click="getRef">获取refs</button>
<button @click="getChildren">获取children</button>
<button @click="getParent">获取parent</button>
getRef() {
console.log(this.$refs.targetDom)
},
getChildren() {
console.log(this.$children)
},
getFather() {
console.log(this.$parent)
}
五、eventBus
使用场景
事件总线机制,思想为Pub/Sub模式的实现。
让所有的事件平行的传递,用于【较小项目】中各级组件的传递。
时序图
优点
export default new Vue()
兄弟节点A引入总线
import EventBus from ‘@/eventBus.js’
兄弟节点A监听事件
mounted() { EventBus.$on(‘changeFromA’, () => { console.log(‘changeFromA’) }) },
兄弟节点B引入总线
import EventBus from ‘@/eventBus.js’
兄弟节点B触发事件
changeMethod() {
EventBus.$emit('changeFromA', {
a: 99,
b: 1,
c: 1
})
},
<a name="umFoi"></a>
### 六、Vuex
<a name="nUSKT"></a>
#### 使用场景
较大的项目。多处需要共享状态。
> Vuex是单向数据流。
<a name="ZJdHg"></a>
#### 时序图
<a name="9C4TG"></a>
#### 优点
- 易追溯。
<a name="9191x"></a>
#### 缺点
- 较大。
<a name="1ZF2R"></a>
#### 示例
this.$store.dispatch(‘actionFn’, data) this.$store.commit(‘mutateFn’, data)
mutateFn (state, obj) { state.obj = obj } get () { return this.$store.state.common.menuActiveName } ```