一、简介
1.1什么是Vuex
官方的定义如下:
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件状态,并以相应的规则保证状态以一种可预测的方式发生改变。
官方的解释看的一团迷雾,简单来说就是把一堆可能很多地方(各组件)会用到的数据管理起来,可以对这些变量进行修改,需要的时候就去取。
2.两个小例子:
1) 在项目中很多地方都要进行请求,vue中一个页面的每个组件都可能会有请求,服务器每次请求都需要判断用户的权限、token等信息,这里就可以把用户的权限信息、token等放入一个单独的仓库里面进行管理,每个组件都能对其状态(说白了就是值)进行读取、修改。
说到这里,可能有很多人就会说,这些信息直接放进cookies和session里面就行了啊,干嘛还要单独弄一个空间来存放这些信息。实际上,更多的时候是使用cookie与vuex配合一起使用,vuex获取信息,cookie控制时间和token的销毁。
2) 基于Vue的组件开发,肯定避免不了组件间的传参,父子传参还好说,直接使用prop传,或者父组件调用子组件的函数(使用emit/on)。但涉及到非父子组件之间的传参,常规的办法是建立一个空组件event bus,通过这个中间组件来传参,但这种方式只适用于不涉及到大量的信息传递,涉及到多个组件之间传参的时候,显然event bus就不适用了,这时候vuex就派上用场了,后面会对比vuex和event bus的区别。
所以,说白了,你可以把vuex看做一个数据库,把可能多个组件会用到的共享的参数放到里面,每个组件都可以获取和修改这些数据,用来解决多组件共享一个数据和组件间的通信问题。
说起存储数据,可能有些小伙伴或会想起localstorage,需要注意的是,vuex适用于单页面开发的,如果刷新页面了,或者页面跳转了(注意,是跳转页面,不是页面中组件改变),那vuex中的数据就自动清空了。而localstorage是一种物理存储,是针对于整个应用(application)的,页面刷新数据依然存在。后面也会介绍vuex和localStorage的区别。
二、应用原理
1.原理简述
既然说到vuex相当于一个数据库,那肯定就要定义里面存储那些信息、如何读取或者使用里面的变量,定义的这个文件就是store.js,现在来讲这个store.js里面是如何定义要用到的变量和使用变量的方法的。
store.js里面主要分为了一下几个模块:
1)state: 这里面就定义了我们存入的变量,它可以是任何类型的变量,在此定义之后,才能在vue的组件里面获取。
2)mutation: 定义更改state中变量状态的函数,所有对state中变量的修改只有一条途径,就是通过调用mutation中定义的对变量的操作方法,通过在组件中使用storage.commit(“定义的函数名”,参数)来修改、提交、删除state中的变量。
3)action:刚才说的mutation中定义的方法是同步的,假若想支持异步,比如在其中加入axios请求,就需要在action中定义函数,然后将mutation中的函数作为回调,执行storage.commit(“定义的函数名”,参数),达到异步的效果。关于mutation和action后面会单独分析。
4)getter类似于组件中的计算属性,当需要对state里面的变量进行一些计算再返回的时候,就要使用getter。他会接受state作为顶一个参数,然后从state中获取相应的变量进行计算,返回计算值。
5)module
当state中的变量越来越多,共享参数涉及到的组件也越来越多,如果不进行分类,就会显得比较臃肿,不利于维护,module的作用就是将store分模块,每个模块拥有自己的state/mutation/action,便于维护。
2.了解了以上之后,来看一个简单的例子:
准备好环境之后(搭建vue项目),引入vuex:
npm install vuex --save
src目录下建立store文件夹,文件夹中新建store.js文件,下面是一个简单的store,js:
这样,就可以在组件中引入store,
通过使用store.state.user获取user,
使用store.commit(“setUser”,”fanrui”)修改属性值;
使用dispatch(“commitUser”,”fanrui”)来进行异步的操作。
3.模块化
上面是一个简单的小例子,刚才说了,对于涉及模块比较多,那这里可以引入module,结构目录如下:
一个一个来看,这里将所有状态量分为了六个模块(对应modules下的六个文件),每个文件里面都自定义了自己的state、mutation,action,例如user.js文件下:
然后在统一的一个index.js(这里的index.js就是store.js,可以自己命名)文件中设置每个模块的对外输出:
在组件中使用时只需要引入外层store文件夹即可调用:
Store.state.user.token获取user中的变量
Store.state.user.commit() 调用user中的mutation方法修改变量
Store.state.user.dispatch()调用user中的action的方法异步执行
这就是采用module的方法,有些同学可能觉得每次调用都要写Store.state.user这么长一串比较麻烦,别担心,vuex提供了一个mapActions辅助函数,解决这个问题。具体使用方法在后面解释。
最后一个,解释一下这个getter.js,刚刚说了这个getter相当于计算属性,其实看一下这个getter.js文件,你可能就明白了
getter里面定义了各种计算变量,默认传入的参数就是整个store的state变量,可以利用state中的任意变量来进行计算,得到想要的属性进行输出,在组件中只需引入getter.js,就可以获取相应状态。
三、补充说明
1.Vuex和eventbus
先来看看各组件的传参:
1.1 父—>子:
每个子组件上都有一个prop,需要传递的参数放入其中,父组件可以把参数传给子组件,例如:
父组件中使用子组件,并定义想要传过去的参数:
子组件通过prop接收:
1.2 子—>父:
子组件可以用vue.$emit传递往上传递消息,父组件通过vue.$on回应子组件穿过来的消息:
子组件中:
this.$emit(“todo”,{
res: ”子组件发送的消息”
)
父组件中:
this.$on(“todo”,function(data){
data为传的消息,对data进行处理
})
1.3 兄弟组件间的传递:
emit/on只能在上下层之间传递消息,兄弟之间使用事件总线event bus传递。Evnetbus就相当于一个顺丰快递,将参数在组件传递;首先新建一个传递的媒介bus.js,为空就行,在需要传递的组件中引入bus.js,在一个组件间传出:
bus.$emit(“queryParam”,message)
在另一个组件中接受:
bus.$on(“queryParam”,message=>{…….})
这里多个组件都可以接受同一个组件发出的消息,只要第一个参数名相同就行。
1.4 Eventbus缺点:
这就是组件间的传参,既然已经实现,哪位什么还要引入vuex?
在最开始,我们的项目可能比较简单,但当项目越来愈大,组件越来越多,组件之间的交互也越来越多,组件里就会不断出现$emit这样的代码,主要产生以下几个问题:
1)代码变的冗余,可读性下降(总之就是看着好累,不舒服)
2)对于每一个组件都需要使用emit或者on来处理,设置不同的通信函数(emit、on的第一个参数)
2)很难查找每个事件是从哪里触发的,因为到处都是关于传参的业务逻辑
1.5 小例子说明:
说到这里可能对两者的区别有了些了解,但具体到应用中的区别还不是特别清楚,下面来举一个计数器的小例子。
有一个根组件,姑且叫做S,里面有一属性count。
需求1,写一个组件A作用是实现其中的按钮一次,然后将count计数加一,并在自身的组件中展示当前count的数字;这个比较好办,点击时count计数加1,同时使用event bus将参数count传递到根组件中,进行根组件的展示。
需求2,写一个组件B,作用是将组件S中的count数值为0,这个也好办,直接使用event bus向S组件发起$emit,S接收后,将count置为0;
问题在于,当你点击A组件时,在S组件和A组件上的count都会加1,但是当点击B时,S组件中的count变为0了,但是A组件的count不会发生变化,因为B组件只通知了S,并不知道到A组件会展示count,这就是问题的所在。
有的小伙伴会说,这不简单,在B中在写一个emit传给A就可以了;的确,这是一个办法,但上面说过了,这只适用于组件较少的情况,当你的组件比较多的时候,就会出现问题
比如在这里有多了个C组件、D组件同样是对count进行操作,也要展示count,那我们是不是得在这些组件之间为每一个组件都写一个emit进行传递,假设有N各组件,那是不是就总共得写N*N+1个emit/on?
第二个问题在于,你在添加组件的时候,你可能并不知道有哪些组件使用到了count,就像上面的例子一样,B只通知了S,而没有通知A。假设在一个项目开发过程中,突然有一个需求让你添加一个对count的操作,暂且不说你要写多少个emit,问题在于你首先得知道在之前有哪些组件用到了count,这也是个问题。
所以针对这种类似的问题,使用vuex是不是更加合理呢,下面来看看vuex是怎么解决上面的问题的吧:
第一个解决的是上面通知组件的问题:
引入vuex之后,将我们的countState放进store的state中作为共享变量,将countState映射到count中,这样每个组件中的count就会随着countState的改变而改变(数据是响应式),任何地方对countState的操作都会映射到各组件中,只要仓库中的countState改变了,各组件中的count也随着更新。
第二个是解决所有组件对状态的操作问题:
比如在需求的增加中,突然对count的大小做个规定,小于50,那对应是不是每个组件对countState进行操作的时候都要先判断其值是不是小于50?这个时候vuex提供了mutation方法,提供对countState变量统一的处理方法,此时就可已在这个方法中加入其值小于50的判断,就不用了在每个组件中单独判断了,将这个逻辑放到vuex中处理,组件只需要调用相应commit方法就行,例如下面的方法:
mutation:{
setCountState(state){
if(state.countState < 50)
{
state.countState+=1;
}
else{
state.countState = 0
}
}
1.6 结论
在大型应用方面,vuex是一个比eventBus好的解决方案。
Vuex式结构更加清晰,更易调试。
若果不涉及到大量的共享数据、组件通信,其实eventbus就足够了,但是。。。。个人还是觉得vuex用着舒服点。
- Vuex、LocalStorage:
Vuex和LocalStorage都可以用存储数据,对于LocalStorage我会另写一篇文章来详细的描述其原理,这里只单纯的讲一讲这两者的区别:
2.1 区别:
先说说存储方式,Vuex是存在内存中的,而localStorage是存在文件中的,并且localStorage的存储到小有限制,为5M
LocalStorage以文件形式存到本地,主要用于页面间的传参,不同页面通过访问storage记性传参;而vuex主要实在单页面内进行组件间的传值,为响应式数据(重点),各组件可以对其进行修改。
由此可以得出结论,在数据是静态不变的情况下,可以使用LocalStorage;当数据被多个组件共同使用,且要求数据改变后达到响应数据的变化的要求,则使用vuex。
2.2常见用法:
其实一般这两者可以搭配使用,由于LocalStorage不能相应数据的变化,所以一般将LocalStorage的操作写在vuex的mutation里面,使其随着vuex的变化而进行够改变;而对于多页面或者页面刷新之后,vuex将会被清空,这个时候就需要从LocalStorage里面取数据,填补vuex;不过需要注意的是:
vuex和LocalStorage要进行同步更新,这个就是在vuex数据改变之后,在mutation对应的方法中把值传到LocalStorage中;
存入vuex和LocalStroage是否有失效时间,比如像存储用户名、密码、token等这样的字段,就要控制这些变量失效时间;
不是所有放在vuex里面的变量都要在放到LocalStorage里面一份,对于刷新页面或者页面跳转(注意是页面跳转而不是组件跳转)涉及到需要保存内的数据存到LocalStorage里面就行了。
以上仅为个人理解。