持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情
前言
大家好,今天我们一起来学习一下vue2中组件交互的几种方式。在我们日常开发中无论是父子组件,兄弟组件还是子孙组件等,都避免不了组件间的信息传递,那么不同组件见都是通过什么方式来进行信息传递的呢?接下来我们来一一进行分析。
props/$emit
- 使用场景:父子组件交互
- 使用频率:频繁
第一种组件交互方式也是最常用的组件见的交互方式就是:自定义属性+props和自定义事件+$emit。
- 自定义属性+props:是父组件给子组件传递数据的一种常用方式
主要就是通过在子组件的props中定义所需要的属性,然后父组件在调用时可以以元素属性的方式将数据传递给子组件
- 自定义事件+$emit:子组件可以通过自定义事件的形式向父组件传递数据
我们知道vue的数据绑定是双向的,但是vue的数据流却是单向的,就是说数据只能从父组件向子组件流动,而子组件是不能直接修改父组件的数据的,因此vue为我们提供了自定义事件的接口,让我们可以通过自定义事件+$emit的形式将数据传递给父组件
下面我们用伪代码来展示一下自定义属性和自定义事件的用法
<!--Child.vue-->
<div>
<button @click="clickme">btn</button>
</div>
复制代码
// Child.vue
export default{
props:["name", "age", "sex"],
methods:{
clickme(){
//第一个参数是自定义事件的名称(可任意命名),第二个参数是要向父组件传递的值
this.$emit("clickme",this.name);
}
}
}
复制代码
<!--Parent.vue-->
<Child name="Alvin" age="18" sex="man" @clickme="fn"/>
复制代码
//Parent.vue
export default{
methods:{
fn(val){
console.log(val);//输出Alvin
}
}
}
复制代码
- 使用场景:获取组件实例
- 使用频率:偶尔 第二种方式也可以认为是父子组件交互的一种,该方式的主要原理就是直接获取到组件的实例,拿到实例后就可以通过实例访问组件内部的任何内容。
- $parent:当前组件对应的父组件,可以通过this.parent来访问父组件内容的属性方法等
- $children:当前组件对应的所有子组件,this.children返回的是一个包含所有子组件的数组
- $root:当前组件的根组件,通过this.root访问根组件中的属性和方法
- ref:ref是定义在元素或组件上的一个属性,然后通过this.$refs.属性名来获取DOM元素或组件,一般用于父组件需要调用子组件中的方法的场景
特别说明:以上通过this.xxx访问组件时,在对应的xxx前都应该加个 $ 符号因为该符号在模板中有特殊含义所以不能两个及以上同时出现,否则就不显示了。
- 使用场景:组件的二次封装
- 使用频率:偶尔
第三种组件交互的方式常用于组件的二次封装中,在我们一般的开发过程中很少用到。
- $attrs:该属性中存储的是没有被props所接收的那些属性,比如在某个组件的props中定义了name和age属性,然后在使用该组件时又额外传递了sex属性,那么这个sex属性就会被存储在attrs中。使用时通过指令v-bind来进行绑定
- $listeners:该属性中保存的是当前组件中所有的自定义事件,与attrs同理。使用时通过v-on指令绑定 举例说明:假设现有一个第三方组件el-button,该组件的props中定义了color属性,那么我们在使用el-button时就可以通过传递color属性来改变el-button的字体颜色了,但由于业务需要想要再通过给el-button传递一个bg属性来改变组件的背景色。如果我们直接给el-button传bg属性的话是无效的,因为该组件的props中并未定义bg属性,而我们又不想去修改el-button的源码,这时我们就需要对el-button进行二次封装,比如my-button,然后在my-button的props中定义一个bg属性,这时我们就可以通过使用我们自己定义的my-button组件并传递bg属性来改变组件的背景色了。然而又引出另外一个问题:当我们想通过我们二次封装的my-button组件使用el-button中定义的原始属性时(如color)就又有问题了,你会发现传过去的color并未生效,原因是我们封装的my-button中并没有接收color属性,那有人就说了,我们再定义个color不就可以了吗?当然也不是不行,如果el-button只有那么一两个或者几个属性时,我们在封装时同步定义一下也没问题,但是如果el-button中有几十个属性,我们也一一定义的话不是太麻烦了?诶,这个时候attrs就派上用场了,我们可以利用该属性将el-button中定义但在my-button中未定义却传递了的属性传给el-button,这样就不用重新定义原有组件的属性但又可以通过新组件直接使用传递给原组件了。 说了一大堆感觉有点绕,下面直接代码演示一下:
<!--el-button源码-->
<button :style="{color:color}" ><slot></slot></button>
复制代码
// el-button 源码
props:['color']
复制代码
<!--App.vue-->
<el-button color="red" bg="black" >btn</el-button>
复制代码
以上代码没有二次封装直接使用的是el-button,而el-button并未定义bg属性,因此bg是无效的。下面我们对el-button进行二次封装
<!--my-button.vue-->
<div :style="{'background':bg}">
<el-button><slot></slot></el-button>
</div>
复制代码
//my-button.vue
props:['bg']
复制代码
<!--App.vue-->
<my-button color="red" bg="black" >btn</my-button>
复制代码
此时bg属性是有效的,而color又变成无效了,因为在我们自己封装的组件my-button中并没有定义color,但是如果我们在不额外定义props的情况下还想让el-button原始的属性生效该怎么办呢,这个时候$attrs就派上用场了,我们只需要稍做改动即可,如下:
<!--my-button.vue-->
<div :style="{'background':bg}">
<el-button v-bind="$attrs"><slot></slot></el-button>
</div>
复制代码
这样我们再去使用my-button时color和bg两个属性就都是有效的了。
provide/inject
- 使用场景:父组件向后代组件传递数据
- 使用频率:很少使用 父组件通过provide提供的属性,所有的后代组件都可用通过inject注入的形式使用父组件中通过provide提供的所有属性,主要用于父组件向后代组件传递数据,使用如下;
// Parent.vue
export default{
provide(){
return{
//提供给后代组件的属性,这里可以是属性也可以是方法,可以使用data中定义的属性或methods中定义的方法
name:'Alvin',
hi: this.hello,
sayhello:this.sayHi
}
},
data(){
return {
hello:"Hello Alvin"
}
},
methods:{
sayHi(){
console.log("Hello Alvin")
}
}
}
复制代码
Children.vue
export default{
inject:["name","hi","sayhello"],//使用时直接通过this.xxx访问即可
}
复制代码
eventbus
- 使用场景:兄弟组件间数据交互
- 使用频率:基本不用 在vuex问世前,兄弟组件间数据交互主要就是靠eventbus来实现。eventbus的原理就是利用发布订阅模式,通过创造一个空的vue实例,然后给这个实例上创造一些事件,并通过函数执行传递实参的方式实现组件间的数据交互。这里主要会涉及到实例上的三个属性:
- $on:用于创造并绑定事件
- $emit:用于触发事件并传递数据
- $off:用于移除事件 举例说明:比如现有Child1.vue和Child2.vue两个组件,Child2想要给Child1传递数据,那么我们就可以在main.js初始化vue之前先创建一个空的vue实例并挂在的vue的原型对象上,然后在child1中通过该实例的on创建并绑定一个事件,最后再在child2中通过该实例的emit方法调用child1中定义的事件并传递相关参数便可实现数据的交互了。
// main.js
//创建空的vue实例并挂着到vue的原型对象上
const dv = new Vue();
Vue.prototype.dv = dv;
const vm = new Vue({
...
})
复制代码
// Child1.vue
export default{
methods:{
clickme(opts){
console.log(opts)
}
},
created(){
this.dv.$on('click', this.clickme);//创建click事件并与clickme方法绑定
}
}
复制代码
// Child2.vue
export default{
methods:{
fn(){
this.dv.$emit("click", 'hello world')//调用dv实例中的click方法并传递“hello world”参数给child1
}
}
}
复制代码
vuex
- 使用场景:组件间数据交互
- 使用频率:常用
上面说到vuex问世前,兄弟组件间的数据交换基本上靠的都是eventbus实现,那么自从vuex出现后eventbus就渐渐的退出了。其实不管是兄弟组件还是父子组件或者是后代组件间的数据交互,一个vuex都可以统统搞定。那么vuex到底是个什么东西呢,它为什么这么牛x。下面我们来简单介绍一下。
官方给出的解释是:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。其实可以简单理解为:将多个组件共享的变量全部存储在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,它最大的特点是响应式。
vuex主要有五大核心模块:
- state:用于保存共享数据,原则上属性不能直接修改,必须借助mutations修改
- mutations:用于同步修改state中的属性
- actions:用于异步修改state中的属性
- getters:这里也是一些函数,类似于组件中的计算属性
- modules:用于划分模块 关于vuex更多详细信息可参考另一篇文章Vuex的使用介绍了解更多。
总结
本次分享我们共同学习了组件交互的几种方式,并对每种方式的使用场景和使用频率进行一一说明。关于组件交互的分享就到这里了。