写点什么

Vue 组件数据通信方案总结

2021 年 1 月 19 日

Vue 组件数据通信方案总结

背景


初识 Vue.js ,了解到组件是 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>
复制代码


// 子组件<template>  <div class="text">{{title}}</div></template>
<script>export default { name: 'demo', data: function() {}, props: { title: { type: String } },};</script>
复制代码


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



// 父组件<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 事件的处理中心(事件总线),通过他来触发以及监听事件,方便的实现了任意组件间的通信,包含父子,兄弟,隔代组件。


null


// 父组件<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 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。



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



// 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" 传入内部组件 


下面来看个例子



// 父组件<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



头图:Unsplash

作者:季节

原文:https://mp.weixin.qq.com/s/Q6RQ4w4HSqD4hjAQSPncQw

原文:Vue 组件数据通信方案总结

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021 年 1 月 19 日 23:391158

评论

发布
暂无评论
发现更多内容

Javascript | 模拟mvc实现点餐程序

LiOnTalKING

Java mvc 前端 H5

微警务平台搭建,智慧警务系统开发解决方案

t13823115967

智慧警务系统开发 微警务

小程序市场的「App Store」来了!你准备好吃“螃蟹”了吗?

蚂蚁集团移动开发平台 mPaaS

小程序生态 mPaaS appstore

5年Java高工经验,我是如何成功拿下滴滴D7Offer的?

Java架构追梦

Java 学习 架构 面试 滴滴

如何从危机中提炼总结,做好2020年的复盘?

CECBC区块链专委会

复盘 经济

AOFEX交易所APP系统开发|AOFEX交易所软件开发

开發I852946OIIO

系统开发

为什么要在以太坊上构建去中心化缓存层?到底要怎样做呢?

CECBC区块链专委会

以太坊

云原生应用开发框架Quarkus介绍

gaolk

云原生 Quarkus

便民服务多元化,智慧平安小区安防智能化建设

t13823115967

智慧城市

浅谈数据仓库质量管理规范

数据社

数据仓库 数据质量管理 七日更

25道mybatis面试题,不要说你不会

田维常

mybatis

iOS面试基础知识 (五)

iOSer

ios 大厂面试 iOS面试 面试题总结 底层知识

排查指南 | mPaaS 小程序被卡在了三个蓝点

蚂蚁集团移动开发平台 mPaaS

小程序 问题排查 mPaaS

智慧平安社区建设解决方案,小区安防预警平台搭建

WX13823153201

智慧平安社区建设

智慧公安大数据研判分析系统解决方案智慧警务平台搭建

13530558032

数字货币交易所系统开发,区块链交易所搭建

薇電13242772558

区块链 数字货币

Native 与 JS 的双向通信

Minar Kotonoha

我的 500 张技术配图是怎么画的?

小林coding

程序人生 画图软件

jenkins实现接口自动化持续集成(python+pytest+ Allure+git)

行者AI

接口自动化传值处理

行者AI

企业数字化如何转型?想要资产状况及时把控,它的作用至关重要!

一只数据鲸鱼

物联网 数字化 数据可视化 资产管理

平安社区平台解决方案,智慧社区管理服务平台搭建

13530558032

json处理

Isuodut

双循环背景下的全球供应链机遇与挑战

CECBC区块链专委会

供应链物流

盘点 2020 |协作,是另外一种常态

Winfield

领域驱动设计 DDD 协作 远程协作 盘点2020

区块链商品溯源解决方案,区块链全程追溯系统

13530558032

观察者模式

soolaugust

设计模式 观察者模式 七日更

Locust快速上手指南

行者AI

week5 conclusion 分布式缓存架构+消息队列

J

极客大学架构师训练营

规划算法

田维常

算法

高光时刻!美团推出Spring源码进阶宝典:脑图+视频+文档

996小迁

spring 源码 架构 笔记

Hummer 轻量级跨端技术框架详解及实战

Hummer 轻量级跨端技术框架详解及实战

Vue 组件数据通信方案总结-InfoQ