vuex官网:

一.前言

不管是Vue,还是React,都需要管理状态(state),比如组件之间都有共享状态的需要。
什么是共享状态?比如一个组件需要使用另一个组件的状态,或者一个组件需要改变另一个组件的状态,都是共享状态。

如果不对状态进行有效的管理,状态在什么时候,由于什么原因,如何变化就会不受控制,就很难跟踪和测试了。

在软件开发里,有些通用的思想,比如隔离变化,约定优于配置等,隔离变化就是说做好抽象,把一些容易变化的地方找到共性,隔离出来,不要去影响其他的代码。约定优于配置就是很多东西我们不一定要写一大堆的配置,比如我们几个人约定,view文件夹里只能放视图,不能放过滤器,过滤器必须放到filter文件夹里,那这就是一种约定,约定好之后,我们就不用写一大堆配置文件了,我们要找所有的视图,直接从view文件夹里找就行。

根据这些思想,对于状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。

二.什么是Vuex1.什么是状态管理呢?

vuex官网说到的"状态管理模式","集中式存储管理",这些词和"状态管理"都是一个含义.就是管理状态.

我们通常会有很多组件,组件之间可能会共享状态.那么如何定义这个状态呢?定义在某一个组件里面肯定是不合适的,要定义在最外层.

用vue生态圈来说,有多个组件要共享状态,通常状态我们用变量来表示,也就是多个组件之间共享变量.当共享变量变多,我们就是用一个对象来存储,这个对象就是存储共享状态的对象.通常,这个对象放在vue顶层的实例中.其他各个组件都可以使用.

而vue是响应式编程方式,一个组件修改了状态,其他组件能够实时响应么?这就是Vuex实现的功能.他的主要功能:

2.通常什么状态需要放在Vuex中管理呢?

不是所有的状态都要交给vuex来管理的,只有在多界面之间共享的状态,我们才将其交给vuex来管理.比如:

用户登录状态:用户名,头像,昵称等等.很多页面可能都会用到用户的基本信息,像这些统一的信息,我们就可以放在统一的地方进行管理了.

token:用户登录的令牌,某些接口必须有令牌才能访问,那么这些几口就需要共享token

商品收藏,购物车中的物品等.我们在各个界面都可以添加商品搜藏,都可以加购,这时候,就可以将其放入到vuex里面

放在vuex中,不仅能够共享状态,还能够实时响应.

3.Vuex的设计思想

Vuex全局维护着一个对象,使用到了单例设计模式。在这个全局对象中,所有属性都是响应式的,任意属性进行了改变,都会造成使用到该属性的组件进行更新。并且只能通过commit的方式改变状态,实现了单向数据流模式。

Vuex集成到了Vue的官方调试工具devtoolsextension,提供了诸如零配置time-travel调试,状态快照导入导出等高级调试功能.

三.Vuex是如何在多组件间进行状态管理的?2.1.单界面状态管理

之前我们遇到的都是在单界面进行状态管理.单界面的状态管理有3个部分,如下图所示:


第一部分:state
第二部分:view
第三部分:action

三部分是如何工作的呢?通常状态我们会用一个变量来表示,定义在组件的data属性中.

scriptexportdefault{name:"calculate",data(){return{counter:0};}}/script

然后,在页面中通过语法糖直接引用counter变量.counter的值就在页面中显示了.

以上是在单页面中状态管理的流程.

2.2.多界面状态管理

举个例子,比如,我们有一个组件

data(){return{counter:0};},methods:{add(){++},dev(){}}}/script

然后在中引入组件

scriptimportCalculatefrom'./views/calculate'exportdefault{name:'App',components:{Calculate,}}/script

这时,如果想要在中使用中定义的变量counter,可以么?
直接使用肯定会报错,但Calculate和App两个组件的关系是父子组件,可以使用父子组件变量传递的方式实现.

如果不是父子关系呢?需要如何实现呢?我们可以使用vuex.

2.3.Vuex的使用

使用vuex,首先需要安装vuex组件

组件安装好了,下面就来看看怎么用吧.

第一步:添加vuex代码文件夹

在src目录下新建/src/store/通常,vuex都放在store文件夹里面.然后在store下面创建一个文件

第二步:在文件中定义vuex组件.

vuex是一个插件,vue-router也是一个插件,插件的使用方式都是类似的.

1:引入vue和vuex
importVuefrom'vue';importVuexfrom'vuex';
2:安装vuex
(Vuex);
3:创建vuex对象
conststore=({state:{},mutations:{},actions:{},getters:{},modules:{}})

在store中定义了5个对象,这5个对象是固定的.每个对象的含义是什么呢?后面在详细说.

4:导出vuex
exportdefaultstore;
5:在中引入vuex
importVuefrom'vue'importAppfrom'./App'importrouterfrom'./router'importstorefrom'./store/index';=false/*eslint-disableno-new*/newVue({el:'#app',store,router,rer:h=h(App)})

接下来在来看看2.2中多界面状态管理的问题.calculate组件中有一个counter,在父组件中想要使用counter,这时候,这个counter就是一个公共的状态了,可以将其定义在vuex中.

2.4Vuex实现多界面状态管理

下面来看看使用vuex的方式如何实现呢?

1.在vuex的state中定义一个变量counter
//第三步:创建vuex对象conststore=({state:{counter:0}})
2.在中使用$来获取vuex中counter变量
templatedivh2{{$}}/h2/div/template

这样就显示出来了counter变量了.并且,可以在或者,或者任何其他组件中都可以直接使用.如下图所示:


接下来要实现+和-的操作.
这个怎么实现呢?我们可能会这么想

/h2h2{{$}}/h2buttonv-on:click="$++"+/buttonbuttonv-on:click="$"-/button/div/template

$++或者$不就可以了么?
虽然,这样也能达到效果,但是Vuex官网推荐我们不要这样使用,原因是,这样操作完,我们不能跟踪到状态的变化.这是什么意思呢,这就要来看看vuex的设计思想了.

2.5Vuex式的设计思想

Vuex实质是单例模式的设计思想

将共享的状态抽取出来,交给大管家,进行统一管理

之后,每一个视图,按照规定好的规则,执行访问或修改等操作.

这就是vuex背后的思想.

这里规定好的规则很重要.规定好的规则是什么规则呢?我们来看一下vuex官方给出的一个图


这里面一共有5个元素

Vuecompontents

State

Mutations

Action

Devtools

这几个部分都是做什么用的呢?

1.VueComponents

在这个图里面绿色的部分是Vuecompontents(Vue组件),Vue组件可以引用state变量,还可以触发操作修改变量的值.

2.State:

State用来存储的是变量,变量值可以直接渲染到Vue组件上,但是约定好的,Vue组件不可直接修改State的值

3.Mutations和Devtools

如果Vue组件想要修改state中的状态,他不能直接修改State,而是需要执行commit,提交到Mutations,由Mutations触发修改state的状态.为什么要这样呢?这就和Devtools有关系了.

我们看到有一块灰色的Devtools,这是什么呢?这是Vue开发的一款浏览器插件.这个插件可以帮助我们记录每次state中变量修改的状态,为什么要记录state的状态呢?

比如,我们有多个组件同时修改vuex中的一个状态,那么大家都来改,最终这个值是谁改的呢?如果没有记录状态变化,那么我们就不知道是谁改的了.Vue提供的Devtools工具,就可以用来记录每次修改的状态.

但是,如果我们直接修改组件,那就没有经过Devtools的流程,Devtools也就记录不了了.也就是直接从state修改变量值(红色箭头),而不是走蓝色箭头的流程,那么没有经过Devtools,Devtools也不能记录上修改的状态了.


4.Action

Action主要是用来进行异步处理操作的.mutations是用来处理同步操作的,所以,vue组件想要修改变量的时候,直接走mutations就可以了,这样也可以通过Devtools来记录修改情况.但是,如果异步怎么办呢?devtools只能用来记录同步状态,如果出现异步,他就记录不了了.所以,这里多了一个就是用来处理异步操作的.当action处理完了以后,再交给Mutations来处理,这时候就是同步的操作了,Devtools也可以处理了.

什么情况会进行异步操作呢?
发送网络请求.所以在Action模块指向了Back,向后端发送网络请求.

2.6Devtools的使用1.安装Devtools

下面我们就来安装Devtools


找到VueJs的Devtools--添加至Chrome,如下图:


然后安装插件即可.

2.使用Devtools跟踪状态

Chrome安装好Devtools以后,打开控制台,在菜单栏最后多了一个vue,点开可以看到如下界面:


其中,第二个按钮是监控变量状态变化的.


我们如果直接使用$++,在Devtools是无法监控状态变化的,但是如果我们使用mutations就可以监控到状态变化.

3.在mutation中修改counter的值

首先,我们是要在页面实现+和-的逻辑,如下图:


这个逻辑在组件会用到,在组件也会用到,因此我们将其定义在vuex插件中.在插件里面定义两个方法:increase()和decrease(),修改变量counter的值

conststore=({state:{counter:100},mutations:{increase(state){++;},decrease(state){;}}})

这两个方法increase()和decrease(),他们自带的参数就是state.

在调用方如何定义呢?也就是和中应该如何使用Vuex中定义的两个mutations方法呢?

/h2h2{{$}}/h2buttonv-on:click="add()"+/buttonbuttonv-on:click="sub()"-/button/div/templatescriptexportdefault{name:"calculate",data(){return{};},methods:{add(){this.$("increase");},sub(){this.$("decrease")}}}/script

在调用方,我们要使用this.$()的方式来提交变更.


2.7总结

Vuex应用的核心就是store(仓库),“store”基本上就是一个容器,它包含着你的应用中大部分的状态(state)。Vuex和单纯的全局对象有以下两点不同:

Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。

你不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交(commit)mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

四.Vuex的核心概念

Vuex的核心概念一共有5个

State

Getter

Mutations

Action

Modules

这几个概念:其中State,Mutations,Action上面都有提高过.下面重点来看看Getter和Modules.在看Getter之前先来看一个概念:单一状态树

4.1单一状态树

什么是单一状态树呢?

比如我们的个人信息,社保信息存在社保系统里,公积金信息存在公积金系统里,医保信息存在医保系统里.这有有好处也有坏处,好处是信息更安全,彼此隔离;坏处是要是办某一件事,想要所有的信息,就要跑很多地方取.

而单一状态树的含义就是将所有的信息都存在一个store中,如果需要什么数据,直接去那一个store中取就好了.不要在系统中定义多个store,这样不方便管理和维护.

单一状态树的含义:在一个项目只建一个store.

4.2Getter的使用

Getter有些类似于compute计算属性.什么时候使用计算属性呢?当我们需要将一个属性的值经过计算以后显示出来,这时候我们通常使用计算属性.

Getter也是如此:当一个state属性需要计算以后显示出来,我们就可以使用Getter属性.
比如现在要计算counter的平方.

如果不使用计算属性,我们怎么做呢?在组件里我们是这么写的.

h2{{$*$}}/h2
1.使用计算属性计算平方

然后,如果在中也要这么用,就再来一段.观察:这代码很长,不利于维护,我们可以将其放到Getter中.统一计算以后返回

conststore=({state:{counter:100},mutations:{increase(state){++;},decrease(state){;}},getters:{//getter中第一个参数是statepowerCounter(state){*}}})

这里定义了一个计算平方的方法powerCounter(),他的第一个参数是state,所以,我们可以直接拿到counter属性进行操作.接下来在调用方如何调用呢?

h2{{$}}/h2

通过$获取计算属性.

2.在getters中定义方法的第二个参数

如果我们在另一个方法里想要使用其他getters计算方法怎么办呢?在getters中定义的方法还有默认的第二个参数getters
例:计算counter的平方+100
这时候我们可以怎么做呢?如下定义了powerAndAdd方法,其第二个参数默认是getters.

getters:{//getter中第一个参数是statepowerCounter(state){*},powerAndAdd(state,getters){+100}}

我们在powerCounter已经做了平方的操作了,接下来我们可以直接使用这个的结果来计算.
在powerAndAdd方法中,第一个参数依然是state,第二个参数是getters.我们可以通过getters获取到第一个方法powerCounter,然后在第一个方法的基础上+100.

在组件中调用powerAndAdd方法

/h2h2{{$}}/h2buttonv-on:click="add()"+/buttonbuttonv-on:click="sub()"-/buttonh2counter取平方:{{$}}/h2h2平方后+100:{{$}}/h2/div/template

最终效果如下:


3.在getters中自定义带参数的方法

在getter中,前面说了,getters中定义的方法,第一个参数是state,第二个参数是getters.这两个都是默认的.如果我有一个方法想要传自定义参数怎么办呢?
不能直接在getters后面添加,可以使用匿名函数接收自定义参数.比如下面的add方法

getters:{//getter中第一个参数是statepowerCounter(state){*},powerAndAdd(state,getters){+100},add(state,getters){return(num1,num2)=num1+100+num2;}}

在使用的时候,直接传递两个参数就可以了,方法如下:

h2累加+100:{{$(200,500)}}/h2
4.3Mutations的使用1.Vuex中store状态修改的唯一方式:提交Mutations

Mutations主要包含两部分

一部分是事件类型(type)

另一部分是回调函数(handler),回调函数的第一个参数是state

例如:

mutations:{increase(state){++;},decrease(state){;}}

我们可以理解为increase是事件类型type,方法体是回调函数.回调函数的第一个参数是state

mutations方法定义好以后,如果我们想要调用,使用commit提交的方式调用

add(){this.$("increase");},
2.Mutations传递参数

在之前计算页面有一个+和-,如果需要+5,+10,-100这时候怎么处理呢?我们需要定义一个方法,接收参数在Mutation中如何定义参数,又如何传递参数呢?

在Mutation中有两种数据提交的方式

1.第一种数据传递的方式

我们来看看步骤:
第一步:在组件定义两个按钮,一个+5,一个加+10

buttonv-on:click="addCount(5)"+5/buttonbuttonv-on:click="addCount(10)"+10/button

在定义一个方法addCount()

methods:{add(){this.$("increase");},sub(){this.$("decrease")},addCount(num){}}

第二步:在store中定义一个mutation方法,并且接收一个参数,如下increaseCount

mutations:{increase(state){++;},decrease(state){;},increaseCount(state,num){+=num;}}

increaseCount()方法第一个参数是state,我们可以用第二个参数来接收变量.这和getter是不一样的,getter需要写一个匿名函数来接收自定义变量
第三步:在定义好的calculate组件中调用store的increaseCount方法

addCount(num){this.$("increaseCount",num)}

在传递参数的时候,我们将自定义参数放在第二个变量位置.

第四步:查看效果


1.第二种数据传递的方式

第一种数据传递的方式是具体的参数,第二种数据传递的方式传递的是对象.我们来看看第二种

下面来对比比较:
之前使用的是直接传递参数的方式

addCount(num){this.$("increaseCount",num)},

使用对象传递参数怎么写呢?

addCount(num){this.$({type:"increaseCount",num:num})},

需要注意的是,写法不同,含义有略有区别.

方式一传递到mutation中的是具体的参数值.

方式二传递到mutation中的是一个对象.

同样是在mutation中定义方法,并接受一个参数obj

increaseCount(state,obj){(obj);},

第一种方式传递过来的是:



可以看到传递过来是具体参数的内容

第二种方式传递过来的是一个对象


观察右侧控制台,传输传递过来的是一个对象,并且是所有参数的对象.所以,如果增加num数字,需要获取

increaseCount(state,obj){(obj);+=;},
3.Mutation的响应规则

Vuex的store的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新.
但是,需要准守对应的规则

1)增加新属性

比如:我们修改info的name参数.先来看效果


第一步:在store/的state中定义变量info,并定义修改info的方法updateInfo

conststore=({state:{info:{name:"王五",age:58,sex:"男"}},mutations:{updateInfo(state,name){//修改info中name的值["name"]=name;}}})

第二步:在中展示info内容,并修改info的内容

/h2h2info:{{$}}/h2buttonv-on:click="updateInfo"修改name/button/div/templatescriptexportdefault{name:"calculate",methods:{updateInfo(){this.$("updateInfo","赵六")}}}/script

这里直接调用的是store中的mutation方法updateInfo().在updateInfo()方法中,使用["name"]=name的方式重置了name的值,并且在页面立刻响应式的看到了效果

但是,不是在任何情况使用["name"]=name赋值都是响应式的,我们来给info增加一个hobby试一试
第一步:在store/中增加mutation方法,添加爱好hobby,hobby属性之前在info中是没有的

updateInfoHobby(state,hobby){["hobby"]=()}

第二步:定义方法修改hobby

updateInfoHobby(){this.$("updateInfoHobby","篮球")}

第三步:看效果


这就是我们要说的Mutation修改state属性的第一个条件:
要想实现响应式展示,需要提前在store中初始化好属性.如果有些属性是动态添加的,提前不知道怎么办呢?我们需要换一种方式添加

updateInfoHobby(state,hobby){(,"hobby",hobby);}

来看看效果:


还有一种方式:就是使用完整的新对象给就对象赋值.

2)删除属性

当我们需要删除属性的时候,也是使用(obj,prop)可以做到响应式展示

4.Mutation的类型常量

在mutation中,我们定义了很多事件类型(也就是方法名),当我们的项目变大时,vuex管理状态越来越多,需要更新状态的情况越来越多,那么意味着Mutation中的方法越来越多.方法多了,名称就容易出错,所以我们将Mutation中的常量提取出来.放在一个公共文件中定义,下面来看看如何实现:
以修改counter方法为例.我们来将自增方法increase提取出来.

第一步:新增一个文件,定义一个常量INCREASW

exportconstINCREASW="increase"

第二步.在store的mutation中使用常量定义方法名

//引入mutationimport{INCREASW}from'./mutation-types'conststore=({mutations:{[INCREASW](state){++;}}})

第三步:在中引入mutation-types并且使用commit提交到mutation

import{INCREASW}from'../store/mutation-types'add(){this.$(INCREASW);},

这样就提取了变量.当需要修改变量名的时候,我们不用每个地方都修改,只需要修改mutation-types中的变量名的值.

5Mutation同步函数

通常情况下,Vuex要求我们Mutation中的方法必须是同步方法.为什么呢?
主要的原因是,当我们使用devtools工具时,devtools工具可以很好的帮我们捕捉mutation的快照.但如果是异步操作,那么devtools将不能很好地追踪到这个操作是什么时候完成的.

举个例子:
我们将[修改name]这个动作进行异步处理.放在setTimeout中,

updateInfo(state,name){setTimeout(function(){["name"]=name;},1000)}


这个问题就是上面说的,在Mutation中尽量不要执行异步操作,要是执行异步操作,devtools可能跟踪不上.
如果确实有异步操作,那么就使用的功能类似于Mutation,但是它主要是处理异步操作的.下面就来看看Action的使用

4.4Action的使用

上面已经说过了,action的用法和Mutation的用法类似.但action主要是处理异步操作.如何将写在mutation中的updateInfo方法中异步操作替换到action中实现呢?

在mutation中定义的方法

updateInfo(state,name){setTimeout(function(){["name"]=name;},1000)}

在action中定义的方法

actions:{aUpdateInfo(context){setTimeout(function(){("updateInfo")},1000)}},mutations:{updateInfo(state,name){["name"]=name;}}

我们定义了一个和updateInfo对应的方法aUpdate.入参是context,注意这里不是state了,而是整个store.异步操作定义在aUpdate方法中,然后调用Mutation中的方法.
注意:这是官方要求的,action不要自己去修改state,所以修改state的操作都在Mutation中进行.

接下来,在按钮[修改name]的时候,重新定义click事件,这次就不能直接指向mutation了,而是要指向action.
*调用mutation的方法使用:this.$()
*调用action的方法使用:this.$()

updateInfo(){//调用mutation//this.$("updateInfo","赵六")//调用actionthis.$("aUpdateInfo","赵六")},

效果如下图所示:


可以看到这会devtools识别了info信息的变化

4.5Module的使用

Module的含义是模块,我们为什么要在Vue中引入模块呢?

Vue使用的是单一状态树,那么也就是说很多状态会交给vuex来管理

当应用变得复杂是,store也会变得很臃肿,
为了解决这个问题,Vuex允许我们将store分割成模块.每个模块都拥有自己的states,mutations,actions,getters等等

写法如下:

conststore=({modules:{a:{state:{},getters:{},mutations:{},actions:{}},b:{}}})

如上,在store中定义了一个modules,里面有两个模块a和b,a模块中定义了一套getters,states,mutations,actions,b模块也可如此定义

那么定义好以后如何使用呢?下面一个一个来看

1.state调用

在store/文件中定义一个moudules,然后定义state

constmodule1={state:{message:"这是定义在module1中的state"},}conststore=({modules:{module1:module1}}

如上展示,如何调用module1中的message呢?

h2message:{{$}}/h2
2.getter调用

在module1中增加getters

constmodule1={state:{message:"这是定义在module2中的state"},getters:{extraMessage:function(state){+"aaaa";}}}

前面介绍过getters中定义的属性,就相当于computed计算属性.
接下来看看如何调用getters中的计算属性呢?

h2extraMessage:{{$}}/h2

在调用的时候,和state有些不同.这里不需要指定modules模块名.首先回去store中定义的getters查找,抄不到再去modules1模块中查找,所以,我们在定义的时候,尽量不要重名

3.mutation的调用

我们定义一个按钮来更新message的值

constmodule1={state:{message:"这是定义在module2中的state"},getters:{extraMessage:function(state){+"aaaa";}},mutations:{changeMessage:function(state){="替换成新的message"}},actions:{}}

接下来看调用方,定义一个按钮,替换message的值.然后changeMessage

buttonv-on:click="changeMessage"替换message/button
changeMessage(){this.$("changeMessage")}

我们看到在调用mutation中的方法的时候,直接使用的是commit.和调用store中mutation是一样的.
这里还可以传递参数,方式方法也和在store中一样

4.action的调用

我们在修改message信息的地方将其设置为异步修改,写法如下:

constmodule1={state:{message:"这是定义在module2中的state"},getters:{extraMessage:function(state){+"aaaa";}},mutations:{changeMessage:function(state){="替换成新的message"+",bbbb";}},actions:{aChangeMessage:function(context){setTimeout(()={("changeMessage")},1000)}}}

在actions中增加了一个setTimeout,这就是异步的.调用方在调用的时候,需要使用dispatch指向actions的方法

changeMessage(){this.$("aChangeMessage")}

以上就是在modules中定义states,getters,mutations,actions的方法的使用

至此Vuex的用法就全部完事了.