本篇说一下组件通信的问题,父子组件通信,前面的博客中已有说明,vue也推荐props in,event out;兄弟节点通信如何做呢?官方其实也给出了实现方式,我们以下面的场景来实现一下:
上图中,实现如下功能:搜索表单组件中,包含各种搜索条件,当点击搜索按钮时,加载数据到列表组件中渲染。
这里会给出三种实现方式,不涉及合适与否,只为演示。
1、使用父组件进行封装,把所有操作都移到父组件中
2、搜索组件,触发事件到父组件,父组件监听到事件发生,则执行查询操作,传递props 到列表组件,这也是我们前面实现过的方式,这里简单写一个demo。
首先定义我们的组件:SearchComponent 、AppComponent、ListComponent
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo4</title> <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script> </head> <body> <div id="app"> <app></app> </div> <script> var SearchComponent = { template:` <div class="toolbar"> <input type="text" placeholder="keyword" v-model="keyword"/> <input type="text" placeholder="description" v-model="desc"/> <input type="button" value="search" @click="search()" /> </div> `, data:function(){ return { keyword:'', desc:'' } }, methods:{ search:function(){ this.$emit('onsearch',{keyword:this.keyword,desc:this.desc}); } } } var ListComponent = { template:` <div class="list" > {{list}} </div> `, props:['list'] } var AppComponent={ template:` <div class="container"> <search @onsearch="search($event)" ></search> <list :list="datas" ></list> </div> `, components:{ 'list':ListComponent, 'search':SearchComponent }, methods:{ search:function($e){ this.datas=JSON.stringify({ data:[], info:'info' }); } }, data:function(){ return { datas:null } } } var app=new Vue({ el:'#app', components:{ 'app':AppComponent } }); </script> </body> </html>
点击搜索按钮,运行效果如下:
上面的例子非常简单,而且所写代码在前面的博文中都有所介绍,这里就不详述了,在这里数据流流向如下:
1、点击按钮,数据由 search组件流向父组件
2、父组件监听onsearch ,监听到事件后,处理并给list赋值,此时数据由 父组件 流向 list组件
父组件这里的作用就是一个中转站,提供了一种数据流的中转功能。那么如果没有父组件,能否实现上述功能呢,毕竟我们不可能每次兄弟组件通信都创建一个多余父组件过来,这样如果嵌套层数过多也是很大的问题,对于兄弟组件通信的问题,官方也提到了叫做event bus的实现方式,下面我们就实现一下第二种方案,基于event bus:
修改我们的代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo4</title> <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script> </head> <body> <div id="app"> <search></search> <list></list> </div> <script> var eventBus = new Vue(); var SearchComponent = { template: ` <div class="toolbar"> <input type="text" placeholder="keyword" v-model="keyword"/> <input type="text" placeholder="description" v-model="desc"/> <input type="button" value="search" @click="search()" /> </div> `, data: function () { return { keyword: '', desc: '' } }, methods: { search: function () { // this.$emit('onsearch',{keyword:this.keyword,desc:this.desc}); eventBus.$emit('onsearch', { keyword: this.keyword, desc: this.desc }); } } } var ListComponent = { template: ` <div class="list" > {{list}} </div> `, data: function () { return { list: null } }, created: function () { var self = this; eventBus.$on('onsearch', function ($e) { console.log($e); self.list = JSON.stringify($e); }) } } var app = new Vue({ el: '#app', components: { // 'app':AppComponent 'list': ListComponent, 'search': SearchComponent } }); </script> </body> </html>
这里借助一个全局的vue空实例,来实现一个全局的eventbus,当然我们也可以使用或者实现自己的eventbus,这个是比较简单的,(大致思路是:定义一个回调列表数组,定义两个方法,一个on一个emit,on即是向回调数组push key 和对应的function,emit就是触发key对应的function)有兴趣的可以简单做一下,保存后运行即可。
对于简单的兄弟组件通信,其实这种方案或者第一种方案已经满足,但是如果兄弟节点过多或者组件层次很深的时候,使用第一种方案我们必须一层一层的传递几乎重复的代码,使用第二种方案所有组件又全部依赖于全局vue实例或者说全局eventbus,有没有更好的状态管理方案呢?能否把状态管理独立出来呢,这就是我们接下来要说的vuex。
每一个 Vuex 应用的核心就是 store(仓库)。"store" 基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex 和单纯的全局对象有以下两点不同:
-
Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
-
你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交(commit) mutations。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo4</title> <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script> <script src="https://cdn.bootcss.com/vuex/2.3.1/vuex.js"></script> </head> <body> <div id="app"> <search></search> <list></list> </div> <script> // var eventBus = new Vue(); var store = new Vuex.Store({ state: { list: null }, mutations: { search: function (state, payload) { state.list = JSON.stringify(payload); } } }) var SearchComponent = { template: ` <div class="toolbar"> <input type="text" placeholder="keyword" v-model="keyword"/> <input type="text" placeholder="description" v-model="desc"/> <input type="button" value="search" @click="search()" /> </div> `, data: function () { return { keyword: '', desc: '' } }, methods: { search: function () { this.$store.commit("search",{ keyword: this.keyword, desc: this.desc }) //eventBus.$emit('onsearch', { keyword: this.keyword, desc: this.desc }); } } } var ListComponent = { template: ` <div class="list" > {{list}} </div> `, computed:{ list:function(){ return this.$store.state.list; } } } var app = new Vue({ el: '#app', store:store, components: { // 'app':AppComponent 'list': ListComponent, 'search': SearchComponent } }); </script> </body> </html>
这里我们创建了一个全局store,store是唯一的,里面保存着所有的状态(这种状态建议是全局的或者共享的,我们这里假设list组件中的state属于共享,大家不要较真,而search中的state属于组件本身状态),我们做如下约定:不要直接修改状态,要通过提交mutations来修改状态,mutations相当于在react中使用setState去修改状态一样。直接修改会运行时异常。
针对上面的代码,主要包括如下几个知识点:
1、vuex的实例化:直接new Vuex.Store ,创建全局唯一store,通过配置参数,设置state(全局共享的)、mutations(只支持同步操作)
2、vuex和vue的联系,通过new Vue实例时,注入store,这里和前文中注入router类似,注入后,在任何子组件中,就可以通过this.$store来访问store了
3、store中的state是响应式的,所以建议定义为组件计算属性,每次通过mutations提交修改,则可直接驱动view的变化。
本节主要引入vuex,算是vuex的开篇,不介绍过多内容,让我们有一个简单的认识,接下来会向介绍vue-router一样,慢慢的深入其它的方方面面。敬请期待。