likes
comments
collection
share

Vue中8种组件通信方式

作者站长头像
站长
· 阅读数 19

直奔主题

本文将介绍以下8组件通信方式:

  • props/emit
  • v-model
  • v-solt
  • refs/parent/children
  • attrs/listeners
  • provide/inject
  • eventBus
  • vuex

props和$emit方式

父子组件在传参时中常用的一种方式,父组件通过v-bind传入参数,子组件通过props来接收,子组件通过$emit方法传入事件名来触发一个事件,父组件通过v-on像监听原生DOM事件一样来监听这个事件。

// 父组件CompFather
<template>
    <div>
        <comp-son
            :title="title_name"
            @changeTitle="changeTitle"
        />
        <div>父组件的title_name:{{ title_name }}</div>
    </div>
</template>

<script>
    import CompSon from "./CompSon";
    export default {
        name: "CompFather",
        components: {CompSon},
        data() {
            return {
                title_name: "我是初始值"
            }
        },
        methods: {
            changeTitle(val) {
                this.title_name = val;
            }
        }
    }
</script>
// 子组件CompSon
<template>
   <div>
      <div>子组件的titleName:{{title}}</div>
      <button @click="changeTitle">改变</button>
   </div>
</template>

<script>
   export default {
      name: "CompSon",
      props: ['title'],
      methods: {
         changeTitle() {
            this.$emit("changeTitle", "我变了");
         }
      }
   }
</script>

适用场景:适用于直接父子关系(中间无嵌套组件)的组件间进行通信。

v-model方式

像单选框、复选框等输入控件,父组件通过v-model传值给子组件时,会自动传递一个名为value的prop属性,子组件只要通过this.$emit('input',val)就能自动更改v-model绑定的值。

// App.vue
<add-item v-model="input_val" />
<template>
   <input :value="value" type="text" @input="$emit('input', $event.target.value)">
</template>

<script>
   export default {
      name: "AddItem",
      // 如果对应的props字段名不叫value,则需要定义model属性来指定父组件的v-modal绑定的是哪个值
      /*model: {
         prop: "value1",
         event: "input"
      },*/
      props: {
         value: String
      }
   }
</script>

适用场景:v-model适用于在封装input类型的组件时,用于给数据进行双向绑定,如果不使用v-model,则需要在父组件增加一个方法来监听事件然后改变父组件中的值,显得非常麻烦且不简洁。

v-solt

可以实现父子组件单向通信(父向子传值),在实现可复用组件,向组件中传入DOM节点、html等内容以及某些组件库的表格值二次处理等情况时,可以优先考虑v-slot。

<template>
  <div class="parent">
    <h3>父组件</h3>
    <input type="text" v-model="message" />
    <Child>
      <template v-slot:child>
        {{ message }}  <!--插槽要展示的内容-->
      </template>
    </Child>
  </div>
</template>
<script>
import Child from './solt-child.vue'
export default {
  name: 'solt-parent',
  data() {
    return {
      message: '',
    }
  },
  components: {
    Child,
  },
}
</script>
<template>
  <div class="child">
    <h4>子组件</h4>
    <p>收到来自父组件的消息:
      <slot name="child"></slot>  <!--展示父组件通过插槽传递的{{message}}-->
    </p>
  </div>
</template>

refs/refs/refs/parent/$children

  • refs

refs绑定在DOM元素上,可以获取dom元素,我们也可以将refs 绑定在子组件上,从而获取子组件实例。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

<template>
  <comp1 ref="comp1"/>
</template>

<script>
   export default {
      name: "Comp-1",
      data() {
          value: ""
      },
      mounted: {
          console.log("comp1的msg:",this.$refs.comp1.msg);
      }
   }
</script>

适用场景:当使用element-ui组件时,可用于调用组件方法,例如el-table组件的选择表格项,排序等等。

  • $parent/children

我们可以通过this.$parent来获取当前组件的父组件实例(如果有的话),也可以通过this.$children来获取当前组件的子组件实例。

注意的是:this.$children数组中下标不一定对应父组件引用的字组件顺序,例如如果有异步加载的子组件,可能会影响其在children中的顺序,所以使用时尽量使用name去查找对应子组件。

// 父组件
<template>
  <div class="parent">
    <h3>父组件</h3>
    <input type="text" v-model="message" />
    <p>收到来自子组件的消息:{{ child1.message }}</p>
    <Child />
  </div>
</template>
<script>
import Child from './child'
export default {
  name: 'Parent',
  data() {
    return {
      message: '',
      child1: {},
    }
  },
  components: { Child }
  mounted() {
    this.child1 = this.$children.find((child) => {
      return child.$options._componentTag === 'Child'
    })
  },
}
</script>

// 子组件
<template>
  <div class="child">
    <h4>子组件</h4>
    <input type="text" v-model="message" />
    <p>收到来自父组件的消息:{{ $parent.message }}</p>
  </div>
</template>
<script>
export default {
  name: 'Child1',
  data() {
    return {
      message: '',   // 父组件通过this.$children可以获取子组件实例的message
    }
  },
}
</script>

attrs和attrs和attrslisteners

attrs和attrs和attrslisteners都是 Vue2.4 中新增加的属性,主要是用来开发高级组件的。

$attrs包含了除了props以外的所有父组件传递的参数(classstyle 除外)。

props和attrs是互补关系。父组件可以通过‘v−bind="attrs是互补关系。父组件可以通过`v-bind="attrs是互补关系。父组件可以通过vbind="attrs"`将参数传给子组件。

试想一下,当你创建了一个组件,你要接收 param1 、param2 … 等数十个参数,如果通过 props,你需要通过props: [‘aram1’, ‘param2’, ……]等声明一大堆。如果这些 props 还有一些需要往更深层次的子组件传递,那将会更加麻烦。

而使用attrs,你不需要任何声明,直接通过attrs ,你不需要任何声明,直接通过attrs,你不需要任何声明,直接通过attrs.param1、$attrs.param2……就可以使用,而且向深层子组件传递也十分方便。

<template>
  <div>
     <comp2 class="comp2" :style="comp2Style" :id="id" :msg="msg" :changeId="getChangedId" />
  </div>
</template>

<script>
import Comp2 from "./comp2";
export default {
   name: "comp-1",
   components: { Comp2 }
   data() {
      return {
         id: 1,
         msg: "msg",
        comp2Style: "color: pink"
      }
   },
   methods: {
    getChangedId (val) {
      this.id = val
    }
   }
}
</script>

父组件给子组件comp2传了5参数:id,msg,style,class,还有一个方法changeId

<template>
   <div>组件2的$attrs:{{$attrs}}</div>
</template>

<script>
export default {
   name: "comp-2",
   props: {
      id: Number
   },
   data() {},
   mounted() {
      console.log(this.$attrs); // props为{} 时 this.$attrs = { "id": 1, "msg": "msg", changeId: f(){} }
      console.log(this.$attrs); // props为{ id: Numner} 时 this.$attrs = { "msg": "msg",changeId: f(){}}
      console.log(this.$attrs); // props为{ msg: String, id: Number } 时 this.$attrs = { changeId: f(){} }
      console.log(this.$attrs); // props为{ id: Number, msg: String, changeId: f(){}}时 this.$attrs = {}
   }
}
</script>

适用场景:组件之间跨级传参,可以使用$attrs属性,这样使得代码更加简洁,更利于维护。

  • $listeners

包含了父作用域中v-on事件监听器,在创建高层次组件时很有用,用法和'attrs′类似。它可以通过v−on="attrs'类似。它可以通过v-on="attrs类似。它可以通过von="listeners"传入内部组件。

下面这个例子,共有三个组件:A、B、C,其关系为:[ A [ B [C] ] ],A为B的父组件,B为C的父组件。我们实现了:

  1. 父向子传值:组件A通过:messageFromA="message"将 message 属性传递给组件B,组件B通过$attrs.messageFromA获取到组件A的 message 。

  2. 跨级向下传值:组件A通过:messageFromA=“message"将 message 属性传递给组件B,组件B再通过v-bind=”attrs"将其传递给组件C,组件C通过attrs"将其传递给组件C,组件C通过attrs"将其传递给组件C,组件C通过attrs.messageFromA获取到组件A的 message 。

  3. 子向父传值:组件A通过@keyup=“receive"在子、孙组件上绑定keyup事件的监听,组件B在通过v-on=”$listeners"将 keyup 事件绑定在其 input 标签上。组件B input 输入框输入时,便会触发组件A的receive回调,将组件B的 input 输入框中的值赋值给组件A的 messageFromComp ,从而实现子向父传值。

  4. 跨层级向上传值:组件A通过@keyup=“receive"在子孙组件上绑定keyup事件的监听,组件B再通过将其继续传递给C。组件C在通过v-on=”$listeners"来将 keyup 事件绑定在其 input 标签上。当组件C input 输入框输入时,便会触发组件A的receive回调,将组件C的 input 输入框中的值赋值给组件A的 messageFromComp ,从而实现跨级向上传值。

// 组件A
<template>
  <div class="compa">
    <h3>A组件</h3>
    <input type="text" v-model="message" />
    <p>收到来自{{ comp }}的消息:{{ messageFromComp }}</p>
    <CompB :messageFromA="message" @keyup="receive" />  <!--监听子孙组件的keyup事件,将message传递给子孙组件-->
  </div>
</template>
<script>
import CompB from './compB'
export default {
  name: 'CompA',
  data() {
    return {
      message: '',
      messageFromComp: '',
      comp: '',
    }
  },
  components: { CompB },
  methods: {
    receive(e) { // 监听子孙组件keyup事件的回调,并将keyup所在input输入框的值赋值给messageFromComp
      this.comp = e.target.name
      this.messageFromComp = e.target.value
    },
  },
}
</script>
// 组件B
<template>
  <div class="compa">
    <h3>B组件</h3>
    <input type="text" v-model="message" />
    <p>收到来自{{ comp }}的消息:{{ messageFromComp }}</p>
  </div>
</template>
<script>
import CompB from './compB'
export default {
  name: 'CompA',
  data() {
    return {
      message: '',
      messageFromComp: '',
      comp: '',
    }
  },
  components: { CompB },
  methods: {
    receive(e) { // 监听子孙组件keyup事件的回调,并将keyup所在input输入框的值赋值给messageFromComp
      this.comp = e.target.name
      this.messageFromComp = e.target.value
    },
  },
}
</script>
// 组件C
<template>
  <div class="compc">
    <h5>C组件</h5>
    <input name="compC" type="text" v-model="message" v-on="$listeners" /> <!--将A组件keyup的监听回调绑在该input上-->
    <p>收到来自A组件的消息:{{ $attrs.messageFromA }}</p>
  </div>
</template>
<script>
export default {
  name: 'Compc',
  data() {
    return {
      message: '',
    }
  },
}
</script>

provide/inject

在vue.js 的2.x 版本中添加了provide和inject选项。父组件通过provide来提供变量,子组件通过inject来注入变量,不论子组件有多深,只要调用了inject就可以注入provider中的数据,而不局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

  • provideinject只注重“源头”和“终点”,主要用于在高阶组件库中使用,在平常的开发中一般不适用的原因是不方便追溯“源头”,不知道是哪一层声明的和使用了。

  • 子孙层中的provide会覆盖祖父层中相同key的属性值。

  • provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。

  • inject选项应该是一个字符串数组或一个对象,from是选自祖父组件provide的哪个值的key,default指定一个默认值。

  • provideinject 绑定不是可响应的。如果message是string类型,父组件通过input改变message后无法再赋值给messageFromA,如果provideinject的是一个对象时,该数据是可响应的,当对象属性值改变后,messageFromA里的属性值还是可以随之改变的。子孙组件接收到的对象属性值也相应的改变了。

// 1级组件A
<template>
  <div class="compa">
    <h3>this is A component</h3>
    <input type="text" v-model="message.content" />
    <CompB />
  </div>
</template>
<script>
import CompB from './compB'
export default {
  name: 'CompA',
  provide() {
    return {
      messageFromA: this.message,  // 将message通过provide传递给子孙组件
    }
  },
  data() {
    return {
      message: {
        content: '',
      },
    }
  },
  components: {
    CompB,
  },
}
</script>
// 2级组件B
<template>
  <div class="compb">
    <h4>this is B component</h4>
    <p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
    <CompC />
  </div>
</template>
<script>
import CompC from './compC'
export default {
  name: 'CompB',
  inject: ['messageFromA'], // 通过inject接受A中provide传递过来的message
  components: {
    CompC,
  },
}
</script>
// 3级组件C
<template>
  <div class="compc">
    <h5>this is C component</h5>
    <p>收到来自A组件的消息:{{ messageFromA && messageFromA.content }}</p>
  </div>
</template>
<script>
export default {
  name: 'Compc',
  inject: ['messageFromA'], // 通过inject接受A中provide传递过来的message
}
</script>

适用场景:适用于封装高阶组件,祖先实例不关心哪个后代实例会用到,后代实例不关心数据来源。

事件总线eventBus

使用中央事件总线实际就是创建一个vue实例,利用这个vue实例来传递消息。 注意:使用事件进行全局通信,容易让全局的变量的变化难以预测。

// main.js
import Vue from 'vue'
import App from './App.vue'

Vue.prototype.$bus = new Vue();

new Vue({
  render: h => h(App),
}).$mount('#app')
// 发送事件
this.$bus.$emit("myEvent", "bus msg");
// 接收事件
this.$bus.$on("myEvent", data => {
    console.log(data);
})

适用场景:适用于规模不大的单页面应用的跨级跨兄弟组件间通信。

Vuex

Vuex 采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex中一共有五个核心概念state、getter、Mutation、Action、Module

import moduleA from './moduleA'
//1.安装插件
Vue.use(Vuex)
 
//2.创建对象
const store = new Vuex.Store({
  state:{
    counter:1000
  },
  mutations:{
     changeCount (state, payoad) { // 同步修改counter数据
         state.counter = state.counter + payload
     }
  },
  actions:{
     asyncChangeCount (store) { // 异步修改counter
       setTimeout(() => {
           store.commit('changeCount', 1)
       }, 2000
      }
  },
  getters:{
     doubleCounter(state) {
         return state.counter * state.counter
     }
  },
  modules:{
    a: moduleA
  }
})
//3.导出使用
export default store
<template>
  <div>
    <h2>{{counter}}</h2>
    <button @click="changeStoreState">修改counter</button>
    <button @click="asyncChange">异步修改counter</button>
 </div>    
</template>

<script>
export default {
  name: 'App',
  computed: {
    counter () {
      return this.$store.getters.counter
    }
  },
  methods: {
    changeStoreState () {
      this.$store.commit('changeCount', 1)
    },
    asyncChange () {
      this.$store.commit('asyncChangeCount', 12)
    }
  },
}
</script>

总结

通信方式使用场景
props/$emit直接父子组件传值
v-model封装需要双向绑定的组件时用v-model传值
v-solt在实现可复用组件,向组件中传入DOM节点、html等内容以及某些组件库的表格值二次处理等情况时
refs/parent/children可用于调用高阶组件方法,比如element-ui组件引用
attrs/listeners跨层级组件间传值,$listeners包含了父作用域中的 (不含native的) v-on 事件监听器
provide/inject用于高阶组件库,日常开发不常用,不利于维护
事件总线eventBus适用于跨层级或兄弟组件间通信
vuex用于大型单页应用,多个组件间需要共享状态