Vue 组件数据通信方案总结

标签: dev | 发表时间:2019-09-08 00:00 | 作者:
出处:http://itindex.net/relian

(给前端大全加星标,提升前端技能)

作者:政采云前端团队 公号 / 季节 (本文来自作者投稿)

背景


初识 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>
<divclass="text">{{title}}</div>
</template>

<script>
exportdefault{
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>
importChildfrom"./component/child.vue";

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



   // 父组件    
<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 */
exportdefault{
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.js    
importVuefrom'vue';
importTplfrom'./index.vue';
importstorefrom'./store';

newVue({
store,
render:h=>h(Tpl),
}).$mount('#app');


   // store    
import Vuefrom'vue';
import Vuexfrom'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
}
});

exportdefaultstore;


   // 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";

exportdefault{
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">
<buttonstyle="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>
<child2v-bind="$attrs"v-on="$listeners"></child2>
</div>
</template>

<script>
importchild2from'./child2.vue';
exportdefault{
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>
<child3v-bind="$attrs"></child3>
</div>
</template>

<script>
importchild3from'./child3.vue';
exportdefault{
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>
exportdefault{
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>
importChild1from'./component/child1.vue';
exportdefault{
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>
importchild2from'./child2.vue';

exportdefault{
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>
<child1ref="comp1"></child1>
<child2ref="comp2"></child2>
</div>
</template>

<script>
importChild1from'./component/child1.vue';
importChild2from'./component/child2.vue';
exportdefault{
name:'demo',
data:function(){
return{
title:null,
name:null,
content:'就是我'
};
},
components: {
Child1,
Child2
},
mounted() {
constcomp1 =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>
exportdefault{
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


推荐阅读

(点击标题可跳转阅读)

Web 页面录屏实现

面试加分项之 Nginx 反向代理与负载均衡

JavaScript Errors 指南


觉得本文对你有帮助?请分享给更多人

关注「前端大全」加星标,提升前端技能

好文章,我在看❤️

相关 [vue 数据通信] 推荐:

Vue 组件数据通信方案总结

- - IT瘾-dev
(给前端大全加星标,提升前端技能). 作者:政采云前端团队 公号 / 季节 (本文来自作者投稿). 初识 Vue.js ,了解到组件是 Vue 的主要构成部分,但组件内部的作用域是相对独立的部分,组件之间的关系一般如下图:. 组件 A 与组件 B 、C 之间是父子组件,组件 B 、C 之间是兄弟组件,而组件 A 、D 之间是隔代的关系.

微豆 - Vue 2.0 实现豆瓣 Web App 教程

- - SegmentFault 最新的文章
一个使用 Vue.js 与 Material Design 重构 豆瓣 的项目. 项目网站 http://vdo.ralfz.com/. # 克隆项目到本地 git clone https://github.com/RalfZhang/Vdo.git # 安装依赖 npm install # 在 localhost:8080 启动项目 npm run dev.

浅谈Vue组件在实际项目中的应用

- - JDC | 京东设计中心
Vue.js 是一套构建用户界面的渐进式框架,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. 虽然目前 Vue 已经很火了,但不可否认的是,仍有很多人刚刚开始学习使用 Vue 来构建前端项目,从生疏的初学者到熟练运用 Vue 的过程中,不可避免地会走一些弯路. 为了实现某个功能,也许尝试过很多方法,最终蓦然回首,才发现当初犯下的错误是那么幼稚.

vue快速入门的三个小实例

- - SegmentFault 最新的文章
用vue做项目也有一段时间了,之前也是写过关于vue和webpack构建项目的相关文章,大家有兴趣可以去看下 webpack+vue项目实战(一,搭建运行环境和相关配置)(这个系列一共有5篇文章,这是第一篇,其它几篇文章链接就不贴了). 但是关于vue入门基础的文章,我还没有写过,那么今天就写vue入门的三个小实例,这三个小实例是我刚接触vue的时候的练手作品,难度从很简单到简单,都是入门级的.

vue父子组件通信高级用法

- - SegmentFault 最新的文章
vue项目的一大亮点就是组件化. 使用组件可以极大地提高项目中代码的复用率,减少代码量. 但是使用组件最大的难点就是父子组件之间的通信. . . // 参数就是子组件传递出来的数据.

vue-router+vuex实现加载动态路由和菜单-爱咖啡-51CTO博客

- -
动态路由加载和动态菜单渲染的应用在后端权限控制中十分常见,后端只要加载权限路由进行渲染返回到浏览器就可以. 在前后端分离中,权限控制动态路由和动态菜单也是一个非常常见的问题. 其实我们最最理想的效果是什么呢. 我们访问一个应用,在登录之前有哪些路由是一定要加载的呢. 你看我总结如下,你看下是不是这些:.

vue iview-admin 如何兼容IE浏览器(通用) - Gas_station的博客 - CSDN博客

- -
对于ie我们没有做过多的兼容处理,如果使用者需要兼容ie,可以install babel-polyfill,然后在main.js上面加载这个包即可. 安装babel-polyfill `npm install babel-polyfill --save` 2. src/main.js 引入babel-polyfill `import '@babel/polyfill` 3.