Vue 组件数据通信方案总结

Easter79
• 阅读 668

本文首发于政采云前端团队博客:Vue 组件数据通信方案总结

https://www.zoo.team

背景

初识 Vue.js ,了解到组件是 Vue 的主要构成部分,但组件内部的作用域是相对独立的部分,组件之间的关系一般如下图:

Vue 组件数据通信方案总结

组件 A 与组件 B 、C 之间是父子组件,组件 B 、C 之间是兄弟组件,而组件 A 、D 之间是隔代的关系。

那么对于这些不同的关系,本文主要分享了他们之间可以采用的几种数据通信方式,例如 Props 、$emit / $on 、Vuex 等,大家可以根据自己的使用场景可以选择适合的使用方式。

一、 Prop / $emit

1、 Prop 是你可以在组件上注册的一些自定义特性。****当一个值传递给一个 Prop 特性的时候,它就变成了那个组件实例的一个属性 。父组件向子组件传值,通过绑定属性来向子组件传入数据,子组件通过 Props 属性获取对应数据。

// 父组件<template>  <div class="container">    <child :title="title"></child>  </div></template><script>import Child from "./component/child.vue";export default {  name: "demo",  data: function() {    return {      title: "我是父组件给的"    };  },  components: {    Child  },};</script>

// 子组件

`

`

2、$emit 子组件向父组件传值(通过事件形式),子组件通过 $emit 事件向父组件发送消息,将自己的数据传递给父组件。

Vue 组件数据通信方案总结

// 父组件<template>  <div class="container">    <div class="title">{{title}}</div>    <child @changeTitle="parentTitle"></child>  </div></template><script>import Child from "./component/child.vue";export default {  name: "demo",  data: function() {    return {      title: null    };  },  components: {    Child  },  methods: {    parentTitle(e) {      this.title = e;    }  }};</script>



// 子组件<template>  <div class="center">    <button @click="childTitle">我给父组件赋值</button>  </div></template><script>export default {  name: 'demo',  data() {    return {      key: 1    };  },  methods: {    childTitle() {      this.$emit('changeTitle', `我给父组件的第${this.key}次`);      this.key++;    }  }};</script>

小总结:****常用的数据传输方式,父子间传递。

二、 $emit / $on

这个方法是通过创建了一个空的 vue 实例,当做 $emit 事件的处理中心(事件总线),通过他来触发以及监听事件,方便的实现了任意组件间的通信,包含父子,兄弟,隔代组件。

Vue 组件数据通信方案总结

// 父组件<template>  <div class="container">    <child1 :Event="Event"></child1>    <child2 :Event="Event"></child2>    <child3 :Event="Event"></child3>  </div></template><script>import Vue from "vue";import Child1 from "./component/child1.vue";import Child2 from "./component/child2.vue";import Child3 from "./component/child3.vue";const Event = new Vue();export default {  name: "demo",  data: function() {    return {      Event: Event    };  },  components: {    Child1,    Child2,    Child3  },};</script>



// 子组件1<template>  <div class="center">    1.我的名字是:{{name}}    <button @click="send">我给3组件赋值</button>  </div></template><script>export default {  name: "demo1",  data() {    return {      name: "政采云"    };  },  props: {    Event  },  methods: {    send() {      this.Event.$emit("message-a", this.name);    }  }};</script>



// 子组件2<template>  <div class="center">    2.我的年龄是:{{age}}岁    <button @click="send">我给3组件赋值</button>  </div></template><script>/* eslint-disable */export default {  name: "demo2",  data() {    return {      age: "3"    };  },  props: {    Event  },  methods: {    send() {      this.Event.$emit("message-b", this.age);    }  }};</script>



// 子组件3<template>  <div class="center">我的名字是{{name}},今年{{age}}岁</div></template><script>export default {  name: 'demo3',  data() {    return {      name: '',      age: ''    };  },  props: {    Event  },  mounted() {    this.Event.$on('message-a', name => {      this.name = name;    });    this.Event.$on('message-b', age => {      this.age = age;    });  },};</script>

小总结:****巧妙的在父子,兄弟,隔代组件中都可以互相数据通信。

三、 Vuex

Vuex[1] 是一个专为 Vue.js 应用程序开发的状态管理模式。****它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vue 组件数据通信方案总结

Vuex 实现了一个单项数据流,通过创建一个全局的 State 数据,组件想要修改 State 数据只能通过 Mutation 来进行,例如页面上的操作想要修改 State 数据时,需要通过 Dispatch (触发 Action ),而 Action 也不能直接操作数据,还需要通过 Mutation 来修改 State 中数据,最后根据 State 中数据的变化,来渲染页面。

Vue 组件数据通信方案总结

// index.jsimport Vue from 'vue';import Tpl from './index.vue';import store from './store';new Vue({  store,  render: h => h(Tpl),}).$mount('#app');



// storeimport Vue from 'vue';import Vuex from 'vuex';Vue.use(Vuex);const store = new Vuex.Store({  state: {    count: 1  },  mutations: {    increment(state) {      state.count++;    },    reduce(state) {      state.count--;    }  },  actions: {    actIncrement({ commit }) {      commit('increment');    },    actReduce({ commit }) {      commit('reduce');    }  },  getters: {    doubleCount: state => state.count*2  }});export default store;



// vue文件<template>  <div class="container">    <p>我的count:{{count}}</p>    <p>doubleCount:{{doubleCount}}</p>    <button @click="this.actIncrement">增加</button>    <button @click="this.actReduce">减少</button>  </div></template><script>import { mapGetters, mapActions, mapState } from "vuex";export default {  name: "demo",  data: function() {    return {};  },  components: {},  props: {},  computed: {    ...mapState(["count"]),    ...mapGetters(["doubleCount"])  },  methods: {    ...mapActions(["actIncrement", "actReduce"])  }};</script>

Vuex 中需要注意的点:

Mutation :是修改State数据的唯一推荐方法,且只能进行同步操作。

Getter :Vuex 允许在Store中定义 “ Getter”(类似于 Store 的计算属性)。Getter 的返回值会根据他的依赖进行缓存,只有依赖值发生了变化,才会重新计算。

本段只是简单介绍了一下 Vuex 的运行方式,更多功能例如 Module 模块请参考官网[2] 。

小总结:****统一的维护了一份共同的 State 数据,方便组件间共同调用。

四、 $attrs / $listeners

Vue 组件间传输数据在 Vue 2.4 版本后有了新方法。除了 Props 外,还有了 $attrs / $listeners。

• $attrs: 包含了父作用域中不作为 Prop 被识别 (且获取) 的特性绑定(****Class Style 除外)。当一个组件没有声明任何 Prop 时,这里会包含所有父作用域的绑定 (****Class Style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

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

下面来看个例子

Vue 组件数据通信方案总结

// 父组件<template>  <div class="container">    <button style="backgroundColor:lightgray" @click="reduce">减dd</button>    <child1 :aa="aa" :bb="bb" :cc="cc" :dd="dd" @reduce="reduce"></child1>  </div></template><script>import Child1 from './component/child1.vue';export default {  name: 'demo',  data: function() {    return {      aa: 1,      bb: 2,      cc: 3,      dd: 100    };  },  components: {    Child1  },  methods: {    reduce() {      this.dd--;    }  }};</script>



// 子组件1<template>  <div>    <div class="center">      <p>aa:{{aa}}</p>      <p>child1的$attrs:{{$attrs}}</p>      <button @click="this.reduce1">组件1减dd</button>    </div>    <child2 v-bind="$attrs" v-on="$listeners"></child2>  </div></template><script>import child2 from './child2.vue';export default {  name: 'demo1',  data() {    return {};  },  props: {    aa: Number  },  components: {    child2  },  methods: {    reduce1() {      this.$emit('reduce');    }  }};</script>



// 子组件2<template>  <div>    <div class="center">      <p>bb:{{bb}}</p>      <p>child2的$attrs:{{$attrs}}</p>      <button @click="this.reduce2">组件2减dd</button>    </div>    <child3 v-bind="$attrs"></child3>  </div></template><script>import child3 from './child3.vue';export default {  name: 'demo1',  data() {    return {};  },  props: {    bb: Number  },  components: {    child3  },  methods: {    reduce2() {      this.$emit('reduce');    }  }};</script>



// 子组件3<template>  <div class="center">    <p>child3的$attrs:{{$attrs}}</p>  </div></template><script>export default {  name: 'demo3',  data() {    return {};  },  props: {    dd: String  },};</script>

简单来说,$attrs 里存放的是父组件中绑定的非 props 属性,$listeners 里面存放的是父组件中绑定的非原生事件。

小总结:****当传输数据、方法较多时,无需一一填写的小技巧。

五、 Provider / Inject

Vue 2.2 版本以后新增了这两个 API , 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。 简单来说,就是父组件通过 Provider 传入变量,任意子孙组件通过 Inject 来拿到变量。

// 父组件<template>  <div class="container">    <button @click="this.changeName">我要改名字了</button>    <p>我的名字:{{name}}</p>    <child1></child1>  </div></template><script>import Child1 from './component/child1.vue';export default {  name: 'demo',  data: function() {    return {      name: '政采云'    };  },  // provide() {  //   return {  //     name: this.name //这种绑定方式是不可响应的  //   };  // },  provide() {    return {      obj: this    };  },  components: {    Child1  },  methods: {    changeName() {      this.name = '政采云前端';    }  }};</script>



// 子组件<template>  <div>    <div class="center">      <!-- <p>子组件名字:{{name}}</p> -->      <p>子组件名字:{{this.obj.name}}</p>    </div>    <child2></child2>  </div></template><script>import child2 from './child2.vue';export default {  name: 'demo1',  data() {    return {};  },  props: {},  // inject: ["name"],  inject: {    obj: {      default: () => {        return {};      }    }  },  components: {    child2  },};</script>

需要注意的是: Provide 和 Inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

所以,如果采用的是我代码中注释的方式,父级的 name 如果改变了,子组件this.name 是不会改变的,仍然是 政采云 ,而当采用代码中传入一个监听对象,修改对象中属性值,是可以监听到修改的。

Provider / Inject 在项目中需要有较多公共传参时使用还是颇为方便的。

小总结:****传输数据父级一次注入,子孙组件一起共享的方式。

六、 $parent / $children & $refs

• $parent / $children: 指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。

• $refs: 一个对象,持有注册过 ref 特性[3] 的所有 DOM 元素和组件实例。ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。

// 父组件<template>  <div class="container">    <p>我的title:{{title}}</p>    <p>我的name:{{name}}</p>    <child1 ref="comp1"></child1>    <child2 ref="comp2"></child2>  </div></template><script>import Child1 from './component/child1.vue';import Child2 from './component/child2.vue';export default {  name: 'demo',  data: function() {    return {      title: null,      name: null,      content: '就是我'    };  },  components: {    Child1,    Child2  },  mounted() {    const comp1 = this.$refs.comp1;    this.title = comp1.title;    comp1.sayHello();    this.name = this.$children[1].title;  },};</script>



// 子组件1-ref方式<template>  <div>    <div class="center">我的父组件是谁:{{content}}</div>  </div></template><script>export default {  name: 'demo1',  data() {    return {      title: '我是子组件',      content: null    };  },  mounted() {    this.content = this.$parent.content;  },  methods: {    sayHello() {      window.alert('Hello');    }  }};</script>



// 子组件2-children方式<template>  <div>    <div class="center"></div>  </div></template><script>export default {  name: 'demo1',  data() {    return {      title: '我是子组件2'    };  },};</script>

通过例子可以看到这两种方式都可以父子间通信,而缺点也很统一,就是都不能跨级以及兄弟间通信。

*小总结:***父子组件间共享数据以及方法的便捷实践之一。

**

总结

组件间不同的使用场景可以分为 3 类,对应的通信方式如下:

• 父子通信:Props / $emit,$emit / $on,Vuex,$attrs / $listeners,provide/inject,$parent / $children&$refs

• 兄弟通信:$emit / $on,Vuex

• 隔代(跨级)通信:$emit / $on,Vuex,provide / inject,$attrs / $listeners

大家可以根据自己的使用场景选择不同的通信方式,当然还是都自己写写代码,试验一把来的印象深刻喽。

外部链接

[1] Vuex http://vuex.vuejs.org/zh/

[2] 官网 http://vuex.vuejs.org/zh/

[3] ref 特性 https://cn.vuejs.org/v2/api/index.html?\_sw-precache=a7a4d39c5138496b644f27256d087649#ref

招贤纳士(Recruitment)

招人,前端,隶属政采云前端大团队(ZooTeam),50 余个小伙伴正等你加入一起浪~ 如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变既定的节奏,将会是“5年工作时间3年工作经验”;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊… 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手参与一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com

本文分享自微信公众号 - 政采云前端团队(Zoo-Team)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k