龙空技术网

vue组件间的九种通信方式

墨染沐苒 67

前言:

而今你们对“vue组件实例是什么”都比较关心,咱们都需要学习一些“vue组件实例是什么”的相关知识。那么小编同时在网上搜集了一些关于“vue组件实例是什么””的相关内容,希望朋友们能喜欢,姐妹们一起来了解一下吧!

前言

Vue组件实例间的作用域是相互独立的,而通常一个页面是由很多个组件构成,这些组件可能又嵌套了组件,形成了一个关系网图,它们的关系可能是像下图中一样,大致分为两种使用场景,父子组件间通信和非父子组件间通信,父子组件间通信又分为直接父子关系和间接父子关系。vue提供了多种通信方法,针对不同的通信需求,选择最合适的通信方式能帮助我们提高开发效率。本文简要介绍了九种通信方式以及适用场景,记录下在学习中的收货。

父子组件间的通信props和$emit

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

// 父组件CompFather<template>    <div>        <comp-son            :title-name="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:{{titleName}}</div>      <button @click="changeTitle">改变</button>   </div></template>​<script>   export default {      name: "CompSon",      props: {         titleName: {            type: String,            required: true         }      },      methods: {         changeTitle() {            this.$emit("changeTitle", "我变了");         }      }   }</script>复制代码

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

$attrs和$listeners

$attrs包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件。

// Comp1.vue<template>   <comp2      :flag="flag"      :id="id"      :msg="msg"   /></template>​<script>   import Comp2 from "./comp2";   export default {      name: "comp1",      components: {Comp2},      data() {         return {            id: 1,            msg: "comp1's msg",            flag: true         }      }   }</script>复制代码
// Comp2.vue<template>   <div>      <div>comp2的$attrs:{{$attrs}}</div>      <comp3 v-bind="$attrs" />   </div></template>​<script>   import Comp3 from "./comp3";   export default {      name: "comp2",      components: {Comp3},      props: {         flag: Boolean      },      mounted() {         console.log(this.$attrs); // { "id": 1, "msg": "comp1's msg" }      }   }</script>复制代码
// Comp3.vue<template>   <div>comp3的$attrs:{{ $attrs }}</div></template>​<script>   export default {      name: "comp3",      props: {         msg: String      },      mounted() {         console.log(this.$attrs);  // { "id": 1 }      }   }</script>复制代码

这里Comp2的props接收了Comp1传入的flag,因此Comp2中的$attrs没有flag参数,Comp3接收了msg,因此$attrs只有id。

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

$listeners包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。

当我们要封装一个input类型的组件时,我们可以通过.native的方式监听它的原生事件。

// App.vue<my-input @input.native="inputData" :value="value" />复制代码
// MyInput.vue<template>   <input         type="text"         :value="value"         @input="$emit('input',e.target.value)"      ></template>​<script>   export default {      name: "MyInput",      props: {         value: String      },   }</script>复制代码

但如果此时的inputlabel标签包裹了一层,input不再是根组件时,父组件的.native将监听失效,于是vue给我们提供了$listeners属性来解决这个问题。

// MyInput.vue<template>   <label>      value:{{ value }}      <input         type="text"         :value="value"         @input="$emit('input',e.target.value)"      >   </label></template>复制代码

以下是使用$listeners属性进行监听:

// App.vue<template>   <my-input v-model="value" @focus="onFocus" @input="inputData" /></template>​<script>   export default {      name: "App",      data() {          value: ""      },       onFocus() {            console.log("获得焦点");        },​        inputData(val) {            console.log(val);        }   }</script>复制代码
<template>   <label>      value:{{ value }}      <input         type="text"         :value="value"         v-on="inputListeners"      >   </label></template>​<script>   export default {      name: "MyInput",      props: {         value: String      },      computed: {         inputListeners: function () {            const vm = this            // `Object.assign` 将所有的对象合并为一个新对象            return Object.assign({},               // 我们从父级添加所有的监听器               this.$listeners,               // 然后我们添加自定义监听器,               // 或覆写一些监听器的行为               {                  // 这里确保组件配合 `v-model` 的工作                  input: function (event) {                     vm.$emit('input', event.target.value)                  }               }            )         }      },   }</script>复制代码

适用场景:当需要监听子组件的原生事件,且子组件可能是经过重构的组件时,可以使用$listeners来代替.native监听,当然也不只是包含原生事件监听器,只是在这里监听原生事件比较合适,我们还有其它方式来做事件监听。

$children/$parent/$root

$children是当前实例的直接子组件,不保证顺序,数组顺序不一定是子组件在该父组件中的渲染顺序,也不是响应式的。

如果当前实例有父组件的话,$parent则是当前实例的父实例。

$root是当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过this.$root来访问。

<script>   export default {      name: "MyComp",      data() {          value: ""      },      mounted: {        console.log(this.$parent);        console.log(this.$children);        console.log(this.$root);      }   }</script>复制代码

缺点:$children获取到的实例不保证顺序,因此当有多个子组件时可能获取到的不是自己想要的那一个。当组件嵌套多级时,可能会出现$parent.$parent.$parent...的情况,对于后续维护不友好。

适用场景:尽量使用其它方式来代替$parent/$chilren

ref

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

// App.vue<template>  <comp1 ref="comp1"/></template>​<script>   export default {      name: "App",      data() {          value: ""      },      mounted: {          console.log("comp1的msg:",this.$refs.comp1.msg);      }   }</script>复制代码

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

v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突。

// App.vue<add-item v-model="input_val" />复制代码
<template>   <div class="add">      <input         :value="value"         type="text"         placeholder="What needs to be done?"         @input="inputVal"      >   </div></template>​<script>   export default {      name: "AddItem",      // 如果对应的props字段名不叫value,则需要定义model属性来指定父组件的v-modal绑定的是哪个值      /*model: {         prop: "value1",         event: "input"      },*/      props: {         value: {            type: String,            required: true         }      },      methods: {         inputVal(e) {            this.$emit("input", e.target.value);         }      }   }</script>复制代码

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

Sync修饰符

syncv-model非常类似,也是适用于需要子组件改变父组件的值时。 :isShow.sync="isShow"其实是 @update:isShow="bol=>isShow=bol"语法糖

<template>    <div>        <input type="button"               value="我是父组件中的按钮"               @click="show">        <child :isShow.sync="isShow" v-show="isShow"/>    </div></template><script>    import child from "@/components/child"    export default {        data() {            return {                isShow:false            }        },        components:{            child        },        methods:{            show(){                this.isShow=true;            },            changeIsShow(bol){                this.isShow=bol;            }        }    }</script>复制代码
<template>    <div>         我是一个子组件,我在红色的海洋里!        <input type="button" value="点我隐身" @click="upIsShow">    </div></template><script>    export default {        methods:{            upIsShow(){                this.$emit("update:isShow",false);            }        }    }</script>复制代码
provide/inject

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。

provide在父组件中将子组件要使用的数据抛出,然后在子组件中inject注入要使用的数据,它不像使用props需要层层传递,中间不需要这个数据的地方也需要声明,provideinject只注重“源头”和“终点”,主要用于在高阶组件库中使用,在平常的开发中一般不适用的原因是不方便追溯“源头”,不知道是哪一层声明的和使用了。

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

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

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

// 父组件provide: ["message"],    // 子组件inject: {    msg: {        from: "message",        default: "default value"    }}复制代码
// 父组件CompFather<template>   <div>      <comp-son />   </div></template>​<script>   import CompSon from "./CompSon";   export default {      name: "CompFather",      components: {CompSon},      provide: {         msg: "父组件msg"      }   }</script>复制代码
// 子组件 CompSon<template>   <div>      子组件   </div></template>​<script>   export default {      name: "CompSon",      inject: ["msg"],      mounted() {         console.log(this.msg);  // 父组件msg      }   }</script>复制代码

provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。例如provideinject的是一个对象时,该数据是可响应的。

// 当provide/inject的是一个对象时,是可响应的,子组件改变值,父组件也会改变,父组件改变子组件也会改变provide() {   return {      msg: this.msg   }},    data() {    return {        msg: {            title: "父组件msg"        }    }}复制代码

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

非父子组件间的通信中央事件总线 bus

使用中央事件总线实际就是创建一个vue实例,利用这个vue实例来传递消息。

使用方式一:

// 定义一个bus文件import Vue from "vue";const bus = new Vue();export default bus;复制代码
// 引入bus文件import bus from "@/bus";bus.$emit("myEvent", "bus msg")复制代码
// 引入bus文件import bus from "@/bus";bus.$on("myEvent", data => {    console.log(data);});复制代码

使用方式二:

// main.jsimport 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

当我们在开发大型但单页应用时,组件间需要频繁使用修改某些值,这时候使用组件间传参就显得非常繁杂混乱,且不利于管理维护,vue为解决这一问题提供了VueX这个状态管理工具,我们只需要将多个组件需要共享的状态放VueX中进行统一管理,形成一个全局单例管理模式,即可实现组件数据同步。

VueX的将要统一管理的状态放在state中,这样在引用了VueX的页面中就能获取到状态,actions主要是处理例如后台请求的一些异步操作,保证数据的同步,通过dispatch方法调用actions中的方法,当得到异步的数据之后再通过commit调用mutations中的方法改变state。不要在组件中通过$store.state直接修改state数据,这样Devtools是监控不到的,要经过mutations才可以。

这里只是简要介绍通信方式,详细的使用方法参见

[VueX官网] vuex.vuejs.org/zh/

总结

通信方式

适用场景

props/$emit

直接父子组件传值

$attrs/$listeners

$attrs非直接父子组件间传值,$listeners监听原生事件

$children/$parent

一般用其它方式代替,不方便维护

ref

可用于调用高阶组件方法,如注册element-ui组件引用

v-model

封装需要双向绑定的组件时用v-modal进行传参

sync修饰符

用于父子组件间,子组件需要改变父组件值时

provide/inject

多用于高阶组件库,平常开发一般不使用,不利于代码维护

中央事件总线

适用于跨级或兄弟组件间通信。

VueX

用于大型单页面中多个组件需要共享同一状态时。

标签: #vue组件实例是什么