Vuex 详解

  • 一、Vuex 是做什么的?
  • 二、管理什么状态呢?
  • 三、单界面的状态管理
  • 四、多界面状态管理
  • 五、Vuex 核心概念
  • 1、State 单一状态树
  • 2、Getters 基本使用
  • 3、Mutation 状态更新
  • 4、Mutation 传递参数
  • 5、Mutation 提交风格
  • 6、Mutation 响应规则
  • 7、Mutation 常量类型
  • 8、Mutation 同步函数
  • 9、Action 的基本定义
  • 10、Action 的分发
  • 11、Action 返回的 Promise
  • 12、Module
  • 13、Module 局部状态
  • 14、Actions 的写法
  • 15、项目结构

源码工程文件为:

一、Vuex 是做什么的?

官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

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

状态管理到底是什么?

状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面,然后,将这个对象放在顶层的 Vue 实例中,让其他组件可以使用。那么,多个组件是不是就可以共享这个对象中的所有变量属性了呢?

等等,如果是这样的话,为什么官方还要专门出一个插件 Vuex 呢?难道我们不能自己封装一个对象来管理吗?当然可以,只是我们要先想想 VueJS 带给我们最大的便利是什么呢?没错,就是响应式。

如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。不用怀疑,Vuex 就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。

二、管理什么状态呢?

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

三、单界面的状态管理

我们知道,要在单个组件中进行状态管理是一件非常简单的事情。

什么意思呢?我们来看下面的图片。

Vue.js_40_Vuex 详解_异步操作

这图片中的三种东西,怎么理解呢?

  • State:不用多说,就是我们的状态。(你姑且可以当做就是 data 中的属性)
  • View:视图层,可以针对 State 的变化,显示不同的信息。(这个好理解吧?)
  • Actions:这里的 Actions 主要是用户的各种操作:点击、输入等等,会导致状态的改变。

四、多界面状态管理

Vue 已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?

多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新),不同界面的 Actions 都想修改同一个状态(Home.vue 需要修改,Profile.vue 也需要修改这个状态),也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个试图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的。状态1/状态2/状态3 你放在自己的房间中,你自己管理自己用,没问题。但是状态a/状态b/状态c 我们希望交给一个大管家来统一帮助我们管理!!!

没错,Vuex 就是为我们提供这个大管家的工具。

全局单例模式(大管家)

我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理,之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作,这就是 Vuex 背后的基本思想。

Vue.js_40_Vuex 详解_封装_02

谷歌浏览器的 Vue.js devtools

Vue.js_40_Vuex 详解_异步操作_03

使用方法

我们还是实现一下之前简单的案例。

Vue.js_40_Vuex 详解_异步操作_04

首先,我们需要在某个地方存放我们的 Vuex 代码:

这里,我们先创建一个文件夹 store,并且在其中创建一个 index.js 文件

Vue.js_40_Vuex 详解_异步操作_05

其次,我们将其挂载到 Vue 实例中,我们让所有的 Vue 组件都可以使用这个 store 对象,来到 main.js 文件,导入 store 对象,并且放在 new Vue 中

Vue.js_40_Vuex 详解_响应式_06

这样,在其他 Vue 组件中,我们就可以通过 this.$store 的方式,获取到这个 store 对象了。

Vue.js_40_Vuex 详解_响应式_07

Vue.js_40_Vuex 详解_响应式_08

好的,这就是使用 Vuex 最简单的方式了。

我们来对使用步骤,做一个简单的小节:

  • 1.提取出一个公共的store对象,用于保存在多个组件中共享的状态
  • 2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
  • 3.在其他组件中使用store对象中保存的状态即可

通过 this.$store.state.属性​ 的方式来访问状态,通过 this.$store.commit('mutation中方法') 来修改状态

注意事项:

我们通过提交 mutation 的方式,而非直接改变 store.state.count。

这是因为 Vuex 可以更明确的追踪状态的变化,所以不要直接改变 store.state.count 的值。

五、Vuex 核心概念

1、State 单一状态树

Vuex 提出使用单一状态树,什么是单一状态树呢?英文名称是 Single Source of Truth,也可以翻译成单一数据源。

但是,它是什么呢?我们来看一个生活中的例子。

我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。

这个和我们在应用开发中比较类似:

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

2、Getters 基本使用

有时候,我们需要从 Store 中获取一些 state 变异后的状态。

我们可以在 Store 中定义 getters:

Vue.js_40_Vuex 详解_响应式_09

Vue.js_40_Vuex 详解_异步操作_10

Vue.js_40_Vuex 详解_响应式_11

3、Mutation 状态更新

Vuex 的 store 状态的更新唯一方式:提交 Mutation。

Mutation 主要包括两部分:

  • 字符串的事件类型(type)
  • 一个回调函数(handler),该回调函数的第一个参数就是 state。

mutation 的定义方式:

Vue.js_40_Vuex 详解_响应式_12

通过 mutation 更新

Vue.js_40_Vuex 详解_异步操作_13

4、Mutation 传递参数

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

Mutation 中的代码:

Vue.js_40_Vuex 详解_响应式_14

但是如果参数不是一个呢?

比如我们有很多参数需要传递。

这个时候, 我们通常会以对象的形式传递, 也就是说 payload 是一个对象。

这个时候可以再从对象中取出相关的信息.

Vue.js_40_Vuex 详解_封装_15

5、Mutation 提交风格

上面的通过 commit 进行提交是一种普通的方式,Vue 还提供了另外一种风格,它是一个包含 type 属性的对象。

Vue.js_40_Vuex 详解_封装_16

Mutation 中的处理方式是将整个 commit 的对象作为 payload 使用,所以代码没有改变, 依然如下:

Vue.js_40_Vuex 详解_异步操作_17

6、Mutation 响应规则

Vuex 的 store 中的 state 是响应式的,当 state 中的数据发生改变时,Vue 组件会自动更新。这就要求我们必须遵守一些 Vuex 对应的规则:

  • 提前在 Store 中初始化好所需的属性.
  • 当给 state 中的对象添加新属性时, 使用下面的方式:
  • 方式一: 使用 Vue.set(obj, 'newProp', 123)
  • 方式二: 用心对象给旧对象重新赋值

7、Mutation 常量类型

我们来考虑下面的问题:

在 mutation 中, 我们定义了很多事件类型(也就是其中的方法名称)。

当我们的项目增大时,Vuex 管理的状态越来越多,需要更新状态的情况越来越多,那么意味着 Mutation 中的方法越来越多。方法过多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至如果不是复制的时候,可能还会出现写错的情况。

如何避免上述的问题呢?

在各种 Flux 实现中, 一种很常见的方案就是使用常量替代 Mutation 事件的类型。我们可以将这些常量放在一个单独的文件中,方便管理以及让整个 app 所有的事件类型一目了然。

具体怎么做呢?

我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量。定义常量时,我们可以使用 ES2015 中的风格,使用一个常量来作为函数的名称。

Vue.js_40_Vuex 详解_异步操作_18

Vue.js_40_Vuex 详解_异步操作_19

Vue.js_40_Vuex 详解_响应式_20

8、Mutation 同步函数

通常情况下, Vuex 要求我们 Mutation 中的方法必须是同步方法。主要的原因是当我们使用 devtools 时,可以 devtools 可以帮助我们捕捉 mutation 的快照。但是如果是异步操作,那么 devtools 将不能很好的追踪这个操作什么时候会被完成。比如我们之前的代码,当执行更新时,devtools 中会有如下信息。

Vue.js_40_Vuex 详解_异步操作_21

但是, 如果Vuex中的代码, 我们使用了异步函数。

Vue.js_40_Vuex 详解_响应式_22

你会发现 state 中的 info 数据一直没有被改变, 因为他无法追踪到。所以, 通常情况下, 不要在 mutation 中进行异步的操作。

9、Action 的基本定义

我们强调, 不要在 Mutation 中进行异步操作。但是某些情况,我们确实希望在 Vuex 中进行一些异步操作, 比如网络请求,必然是异步的,这个时候怎么处理呢?

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

Action 的基本使用代码如下:

Vue.js_40_Vuex 详解_封装_23

context 是什么?

context 是和 store 对象具有相同方法和属性的对象。也就是说, 我们可以通过 context 去进行 commit 相关的操作,也可以获取 context.state 等。但是注意,这里它们并不是同一个对象,为什么呢?我们后面学习 Modules 的时候,再具体说。

这样的代码是否多此一举呢?

我们定义了 actions,然后又在 actions 中去进行 commit, 这不是多此一举吗?事实上并不是这样,如果在 Vuex 中有异步操作,那么我们就可以在 actions 中完成了。

10、Action 的分发

在 Vue 组件中,如果我们调用 action 中的方法,那么就需要使用 dispatch。

Vue.js_40_Vuex 详解_封装_24

同样的, 也是支持传递 payload。

Vue.js_40_Vuex 详解_封装_25

11、Action 返回的 Promise

前面我们学习 ES6 语法的时候说过,Promise 经常用于异步操作,在 Action 中,我们可以将异步操作放在一个 Promise 中,并且在成功或者失败后,调用对应的resolve 或 reject。

Vue.js_40_Vuex 详解_响应式_26

12、Module

Module 是模块的意思,为什么在 Vuex 中我们要使用模块呢?Vue 使用单一状态树,那么也意味着很多状态都会交给 Vuex 来管理。当应用变得非常复杂时,Store 对象就有可能变得相当臃肿。为了解决这个问题,Vuex 允许我们将 store 分割成模块(Module),而每个模块拥有自己的 state、mutations、actions、getters 等。

我们按照什么样的方式来组织模块呢?

Vue.js_40_Vuex 详解_响应式_27

13、Module 局部状态

上面的代码中, 我们已经有了整体的组织结构, 下面我们来看看具体的局部模块中的代码如何书写。

我们在 moduleA 中添加 state、mutations、getters,mutation 和 getters 接收的第一个参数是局部状态对象。

Vue.js_40_Vuex 详解_响应式_28

注意:虽然,我们的 doubleCount 和 increment 都是定义在对象内部的。但是在调用的时候,依然是通过 this.$store 来直接调用的。

14、Actions 的写法

actions 的写法呢? 接收一个 context 参数对象。

局部状态通过 context.state 暴露出来,根节点状态则为 context.rootStateVue.js_40_Vuex 详解_响应式_29

如果 getters 中也需要使用全局的状态,可以接受更多的参数。

Vue.js_40_Vuex 详解_封装_30

15、项目结构

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

Vue.js_40_Vuex 详解_异步操作_31

如有错误,欢迎指正!