组件的通信
一般来说组件可以有以下几种关系:
Vue.js 可以使用内置的方法来访问组件。
- ref: 用于引用元素或组件。
$parent/$children: 访问父组件或子组件。
使用以上方法访问都可以获取组件实例,可以直接调用组件的方法或访问数据。比如下面的示例中使用ref来访问组件: (部分代码省略)
// fButton组件
<template>
<button class="f-button" :class="[type, iconPosition, size]" @click="onClick">
<span class="word">
<slot>按钮</slot>
</span>
<f-icon :iconName="icon" v-if="icon && !loading"></f-icon>
<f-icon class="loading" iconName="loading" v-if="loading"></f-icon>
</button>
</template>
<script type="text/javascript">
import fIcon from "./Icon";
export default {
name: "fButton",
data() {
return {
information: 'hello'
};
},
props: {
type: {
default: "default",
type: String,
validator(val) {
return ["default", "dashed", "danger", "primary"].indexOf(val) >= 0;
}
},
icon: {},
iconPosition: {
default: "left",
type: String,
validator(val) {
return ["left", "right"].indexOf(val) >= 0;
}
},
loading: {
type: Boolean,
default: false
},
size: {
default: "medium",
type: String,
validator(val) {
return ["medium", "small", "large"].indexOf(val) >= 0;
}
}
},
components: {
fIcon
},
methods: {
onClick() {
this.$emit('click')
}
}
};
</script>
<template>
<div id="app">
<section>
<f-button icon="loading" iconPosition="right" ref="fbutton">Default</f-button>
</section>
</div>
</template>
<script>
import fButton from './components/Button'
export default {
name: 'App',
data() {
return {
message: 'hello'
}
},
components: {
fButton,
}
mounted() {
console.log(this.$refs.fbutton) //获取到fButton组件实例
console.log(this.$refs.fbutton.information) //hello
}
}
</script>
使用 $parent/$children 可以访问到父组件实例或子组件实例,基于当前上下文访问父组件或全部子组件。
但是以上两种方法无法在跨级或兄弟组件之间通信,例如下面的结构:
App.vue
<f-button icon="loading" iconPosition="right" ref="fbutton">Default</f-button>
<f-input size="small" placeholder="请输入内容" :disabled="true"></f-input>
想要在 f-button 组件中访问到引用它的页面 App.vue 中的另一个兄弟组件 f-input,就无法使用上面的两种方法了,得使用 Vuex 和 Bus 这样的解决方案。
provide/inject
前面讲到 ref 和 $parent / $children 在跨级通信时是有弊端的。当组件 A 和组件 B 中间隔了数代(甚⾄不确定具体级别)时,以往会借助 Vuex 或 Bus 这样的解决⽅案,不得不引⼊
三⽅库来⽀持。但也可以使用 Vue.js 内置的 provide / inject 接⼝。
什么是 provide/inject ?
查看Vue.js官方文档可以知道: cn.vuejs.org/v2/api/#pro…
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
下面来看看该 API 的使用方法:
//input.vue 为 App.vue 的子组件
// App.vue
export default {
provide () {
return {
information: 'hello'
}
}
}
// input.vue
export default {
inject: ['information'],
mounted() {
console.log(this.information) //hello
}
}
可以看到,在 App.vue ⾥,我们设置了⼀个 provide: information,值为 hello,它的作⽤就是将 information 这个变量提供给它的所有⼦组件。 ⽽在 input.vue 中,通过 inject 注⼊了从 App 组件中提供的 information 变 量,那么在组件 input 中,就可以直接通过 this.information 访问这个变量 了,它的值也是 hello。这就是 provide / inject API 最核⼼的⽤ 法。
需要注意的是:
provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的
所以,上⾯ App.vue 的 information 如果改变了,input.vue 的 this.information 是不 会改变的,仍然是 hello。
只要⼀个组件使⽤了 provide 向下提供数据,那其下所有的⼦组件都可以通过inject来注⼊,不管中间隔了多少代,⽽且可以注⼊多个来⾃不同⽗级提供的数据。需要注意的是,⼀旦注⼊了某个数据,⽐如上⾯示例中的 information,那这个组件中就不能再声明 information 这个数据了,因为它已经被⽗级占有。
总结: provide / inject 在组件跨级通信时非常有用(无论是隔了多少代)。