前端框架Vue自学之Vuex(八)

我的最新博客在:Secret_wu's coding note

终极目标:掌握和使用Vue(全家桶:Core+Vue-router+Vuex)

本博客目的:记录Vue学习的进度和心得(Vuex,终于到Vuex了,泪目)

内容:学习和使用Vuex。

正文:

Vuex

一、Vuex概念和作用解析

  1、认识Vuex

  Vuex是一个专为vue.js应用程序开发的状态管理模式

  它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex也集成到Vue的官方调试工具,提供了诸如零配置的time-travel调试、状态快照导入导出等高级调试功能。Vuex也是响应式的。

  2、状态管理

  我们可以简单地将状态管理看成把需要多个组件共享的变量(状态)全部存储在一个对象里,然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。

   看上去我们可以直接修改Vue的原型实现共享变量(自己封装也很麻烦),但其不是响应式的。而Vuex就是为了提供一个在多个组件间共享状态的插件并且还是响应式的。

  3、管理什么状态

  有什么状态是需要我们在多个组件间共享的呢?

  如果我们做过大型开发,一定遇到过多个状态,在多个界面间的共享问题。比如用户的登录状态、用户名称、头像、地理位置信息等等。比如商品的收藏、购物车的物品等等。这些状态信息,我们都可以放在统一的地方,对它进行保存和管理(用Vuex),而且它们还是响应式的。

二、单界面到多界面状态管理切换

  1、单界面的状态管理

  

   state储存当前界面(组件)的状态(姑且看成在vue实例中的data中的属性),这个状态是在view(视图层)中显示,当发生一些行为actions时,会修改状态,这个状态也是通过view显示。使用Vue就可以做到单界面的状态管理。

  2、多界面状态管理

  当我们不想只在一个界面进行状态管理,即想多界面状态管理。也就是说对于某些状态(状态1,2,3)来说只属于我们某一个视图,但是也有一些状态(a,b,c)属于多个视图共同想要维护的。通常,状态1,2,3放在自己的房间里,自己管理自己;但是状态a,b,c,我们希望交给一个大管家来统一帮助我们管理,而Vuex就是为我们提供这个大管家的工具。

  全局单例模式(大管家)。我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。之后,我们每个视图,按照我们规定好的规定,进行访问和修改等操作。这就是Vuex背后的基本思想。(单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。)

  首先,安装。Vuex是个插件,先安装,npm install vuex --save(运行时依赖)。

  接着,创建Vuex的文件夹(src下)。通常我们会把使用Vuex的代码放置在一个文件夹内进行管理(类似使用vue-router我们会创建一个router文件夹),所以我们创建一个名为store的文件夹(通常起store仓库,而不是vuex)。

  然后在store文件夹下,创建index.js。导入Vue和Vuex,安装插件(Vue.use(插件)),创建Vuex.store对象(放置state,mutation,actions,getters,modules),接着导出store。然后在main.js中使用,挂载。

  

    

   Vuex.store对象中,state就是放置共享的状态(可以简单先看成变量)。由于store已经挂载在vue实例上,也就是所有的组件都可以使用和这个状态,通过$store来调用这个index.js中的store,即Vuex.store实例对象。然后通过$store.state.X属性来获取state中的X属性。

   官方的图。可以发现组件不是直接地获取和修改state对象的,是有一定流程的。devtools是Vue开发的一个浏览器插件,可以帮我们记录跟踪每次修改state的记录。注意,组件可以直接操作到mutations,但一定是得是同步操作(因为devtools也是同步的),而如果是异步操作,就必须经过actions的流程。而异步操作通常是网络请求,所以actions和后端backend API是可以结合使用的。

  3、devtools和mutations

  devtools是vue开发的一个浏览器插件。假设我们使用Chrome浏览器,打开Chrome网上应用,搜索devtools。

   点击添加,当我们重新打开Chrome浏览器,打开控制台,选项多个vue,说明安装成功。

  

   这个devtools插件就是用来调试vue程序的(包括Vuex)。点击第二个按钮,就是专门调试Vuex的界面,例如我之前在index.js代码中添加了state的一个属性count,浏览器也显示出来了。此外,如果我们是通过mutations修改的状态,也是可以在这里面进行跟踪。这也是官网推荐我们不要在组件中直接修改state的原因之一。

  

   mutations中,通常定义一些方法,而这些方法的默认参数就是state对象,所以操作state对象的一些属性非常容易。

  (mutations里面定义方法)

   然后假如在App.vue调用这个方法,(首先监听事件等),然后在methods中定义方法,使用这个mutation的方法,但注意得使用.commit('方法名'),而不是直接.方法名。

  

   然后用devtools查看,发现的确可以追踪状态的改变。并且选择某次的修改(从右边的记录栏里),可以查看当时的state情况。

三、Vuex核心

  Vuex核心概念:State,Getters,Mutation,Action,Module。State保存状态相关的信息,Getters优点类似组件的计算属性,Mutation通常定义一些操作状态的方法,Action主要做些异步操作,Module用于划分模块,针对不同模型,进行一些保存等操作。

  1、state单一状态树的理解

  state单一状态树(Single Source of Truth,单一数据源)

  例如,在我们国内有很多信息需要被记录,例如上学时的个人档案,工作后的社保记录等,这些信息被分散在很多地方进行管理,有一个你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的地方获取(打印)各种资料信息,最后到一个地方提交证明你的信息无误。(这样做的好处是管理责任划分明显,安全性高。)这种保存信息的方案,低效又不方便管理,以及日后的维护也是一个庞大的工作。

  这个和我们在应用开发中比较类似,如果你的状态信息是保存到多个Store对象中,那么之后的管理和维护等等都会变得特别困难,所以Vuex也使用了单一状态树来管理应用层级的全部状态。单一状态树能够让我们最直接的方法找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

  2、getters基本使用

  有时候,我们需要从store中获取一些state变异后的状态,然后使用这个变异(就是做某些操作)的状态。

  例如基于之前counter例子,使用getters,操作counter变为平方后的值。

  同样地,getters内的方法也是有默认参数state。

  

  然后通过$store.getters.方法名调用。

  

   最后在页面也显示了正确的结果。

  

   总结:getters里面定义的方法是针对state的,虽然它在单个界面使用的时候,非常类似于计算属性。但是如果在多个界面使用时,getters的方法是比计算属性好得多,因为不需要在每个界面都定义和使用计算属性那么麻烦。(这更体现了公共管理state的思想,妙啊)

  此外,getters定义的方法定义中,除了可以传入state,也可以传入getters本身(将其作为方法中的参数)。

    假如我们需要带参数的getters方法,即可以返回一个函数,让这个函数带参数。注意,不能直接想之前一样简单的把参数放入getters方法函数中(因为其本身就有默认参数,放进去也是只是代替默认参数的名称而已,里面调用的还是默认参数,如state,getters等),这样不起作用。  

   

 

   当然也可以写成箭头函数的形式:

  3、mutations的携带参数

  Vuex的store状态的更新唯一方式:提交Mutation。($store.commit()方法)

  Mutation主要包括两部分:字符串的事件类型(type)一个回调函数(handler),该回调函数的第一个参数就是state。例如之前我们的举例中,increment是事件类型,具体的处理就是回调函数。(后一节有说这种格式的提交风格)

  在通过mutation更新数据的时候,有可能我们希望携带一些额外的参数。参数被称为是mutation的载荷payload)。

  mutations可以携带参数,在commit后面附上变量即可,然后在mutation对应的方法里面使用这个参数即可。例如只提交一个参数(变量,对象都行):(当传入多个变量时,可以写成一个对象)

   

  4、Mutation的提交风格

  上一小节,是通过commit进行提交时一种普通的方式。

  Vue还提供了另外一种风格,它是一个包含type属性的对象。此时,后面是一个payload对象,即下例中的count是一个对象。(此时代码写的就和之前不一样)

   (直接引用count是一个payload对象,调用值的话,写成count.count才合理(前一个count是形参,后者的count指的是原来的count值))

   所以应该写成这样比较合理:

   

  5、Mutation响应规则(数据的响应式原理)

   Vuex的store中的state是响应式的(源码中是通过Dep,Dep是data每个对象包括子对象都拥有一个该对象, 当所绑定的数据有变更时, 通过dep.notify()通知Watcher),当state中的数据发生改变时,Vue组件会自动更新。

  (简单来说,state中的属性都会被加入到响应式系统,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面发生刷新)

  这就要求我们必须遵守一些Vuex对应的规则:提前在store中初始化好所需的属性。当给state中的对象添加新的属性时,使用下面的方式:

  方式一:使用Vue.set(obj, 'newProp',123) 。这样做是响应式的,把在内部把‘newProp’也加入了响应式系统。

  删除属性时,用Vue.delete(obj, 'delProp') .

  方式二:用新对象给旧对象重新赋值

  注意,如果使用obj.newProp= 'xx',不是响应式的(因为之前没有这个obj.newProp的Dep)。此外,用delete方法删除属性也是非响应式的,因为这些方式不能加入到原来的响应式系统中。(这也是Vue响应式的内在要求,不仅是vuex,组件等等都是。)

  6、Mutation的类型常量

  在mutation中,我们定义了很多事件类型(也就是其中的方法名称)。当项目增大时,vuex管理的状态越来越多,需要更新状态的情况越来越多,那么意味这mutation的方法越来越多。使用者需要大量精力去记住这些方法,甚至在多个文件间来回切换,查看方法名称,很麻烦。

  也就是说,有时候我们需要记mutation的type,当定义和commit的时候都会用到,但是两边复制粘贴,太麻烦了。这时候,可以使用mutation的类型常量。

  首先,在store文件夹下,创建mutations-types.js文件。里面定义一些关于mutation类型的常量(并导出),例如下例,此时INCREMENT就相当于‘increment’。

   (index.js) 

   然后,在App.vue导入这个常量。(不是默认导出,记得用大括号哦)

  (App.vue)

   对应我们commit使用这个常量,就很简单了,不用来回看了。

  

   但是我们还得保证mutation定义的时候,方法名没有写错,所以,我们也把这个常量导入到index.js中。

  

   修改成['XX']的方式,然后用常量代替。

  

   

  7、actions的使用详解

  通常情况下,vuex要求我们mutation中的方法必须是同步的

  主要的原因是当我们使用devtools时,可以devtools可以帮助我们捕捉mutation的快照。但是如果是异步操作,那么devtools将不能很好地追踪这个操作什么时候会完成。

   所以不要在mutation中进行异步操作。

  但是在某些情况,我们确实想在vuex中进行一些异步操作,比如网络请求。

  action类似于mutation,但是是用来代替mutation进行异步操作的。

  action的基本使用:

  action中定义的方法也有默认参数,但不是state,而是context(上下文),目前可以理解为store。但注意修改state的唯一途径是通过mutation

  

   然后由于执行的是异步操作,对应的App.vue组件使用的方法也要修改。使用dispatch()(派遣)。

  

   此时,进行的异步操作可以被devtools捕获了。

  同样地,action也可以带参数的。

  (App.vue)

    (index.js)

   如果我们想在异步操作完成的时候,出现提示信息。可以结合使用Promise(ES6新增)。即把异步操作进行Promise封装。

  (index.js)

  然后在App.vue中使用then(),即执行异步请求成功后的操作。(相当于通过action中转,在返回Promise对象后使用then()函数)

   

  8、modules的使用

  Vue使用单一状态树,那么也意味着很多状态都会交给Vuex管理,当应用变得很复杂时,即写states,mutations,actions,getters代码过多时,会显得store对象比较臃肿。

  为解决这个问题,Vuex允许我们将store分割成模块(module),而每个模块拥有自己的states、mutations、actions、getters等。

  

   取模块中的某状态时,是使用store.state.a(a是在store中的模块名),如果进一步取模块a的state某属性,假如name,直接使用store.state.a.name即可,不需要写成store.state.a.state.name。

  在模块定义的mutation方法,在使用时,也是直接使用$state.commit('方法名')使用方法,其会先在store中的mutation搜索该方法,如果没有,则去模块中搜索,从而调用。所以模块的方法名最后不要和stroe的mutation方法重复。

  模块中的getters方法,也是类似于上述一样,直接通过普通的$store.getters.方法名来使用方法。

  然后,模块中的getters的方法可以想之前说的一样,使用当前getters作为方法参数。

  此外,模块中的getters的方法还可以有第三个默认参数,叫rootState,特指源store的state。

  模块中的actions。之前提过,actions中的方法,里面有一个默认参数,即context(上下文),但注意,此时store.commit(),即context.commit(),这个store是模块自己的store。但是这个context里面有很多属性,包括根store和根getters。 

  9、store文件夹的目录结构

  当我们的vuex帮助我们管理过多的内容时,好的项目结构可以让我们的代码更加清晰。

 

   之前我们把所有关于vuex的代码写到一块了,当项目比较大时,不方便管理,所以需要进行构造好的目录结构,即对一些代码进行抽离。

  state代码:(index.js里面)把state抽离成一个对象,放在store外面,然后在里面使用这个state对象。

  

   mutation:(重新创建一个mutations.js文件,在store目录下)

  然后把index.js中的mutation对象写进去,再导出。最后在index.js中引用。

  actions:也是类似上述的做法。(actions.js)

  getters:也是类似上述的做法。(getters.js)

  modules:新建一个modules文件夹(store目录下),由于模块可以有多个,然后在这个modules文件夹放置一个一个模块文件(.js文件,也是导出),最后在index.js按需引用(但引用的时候,最好是a: moduleA,给moduleA一个名字a)。

原文地址:https://www.cnblogs.com/xinkuiwu/p/12115274.html