Vuex是做什么的?

官方解释:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

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

状态管理到底是什么?

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

等等,如果是这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?

  • 当然可以,只是我们要先想想VueJS带给我们最大的便利是什么呢?没错,就是响应式
  • 如果你自己封装实现一个对象能不能保证它里面所有的属性做到响应式呢?当然也可以,只是自己封装可能稍微麻烦一些。
  • 不用怀疑,Vuex就是为了提供这样一个在多个组件间共享状态的插件,用它就可以了。

管理什么状态呢?

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

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

单界面的状态管理

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

image-20200702162659391

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

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

image-20200702163322182

多界面状态管理

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

  • 多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
  • 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)

也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个试图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的

  • 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。
  • 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!
  • 没错,Vuex就是为我们提供这个大管家的工具。

全局单例模式(大管家)

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

Vuex的基本使用

首先通过npm install vuex --save下载安装Vuex。接着创建目录store,再目录下编写index.js

code-snapshot (61)

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

code-snapshot (62)

如何获取使用其中的值呢?例如state对象中的counter值。只需要通过$store.state.counter即可获取到相应的属性值。

HelloVuex.vue文件中

image-20200702165558747

App.vue文件中注册组件对象,并也获取到state对象中的counter值,并通过按钮对其值进行加减

image-20200702165658620

查看效果,可以发现这值是公共的每个组件都能对其操作并获得其对应属性的值。

GIF1658

不过直接通过$store.state.counter对其进行加减是不符合规范的。这里只做效果演示

Vuex状态管理图例

image-20200702170550064

通过官方图例,我们发现如果我们要修改State就要通过Mutations来对其进行操作,才能被官方的开发工具进行检测。否则我们如上文一样直接修改State的属性值,是不能被Devtools检测到我们进行的操作,不利于我们的调试!Actios是用来进行异步操作的,发送网络请求

我们对上文中的效果展示进行修改,通过Mutations来对State进行操作

首先修改store文件中的index.js文件,再mutatios创建两个方法,方法参数值为state对象

image-20200702171618481

接着修改App.vue

image-20200702171736653

同样绑定两个方法,方法中通过this.$store.commit()传入的字符串值对应着mutatios的方法名。

查看页面效果,能正常对其操作,并能被开发工具检测记录

GIF1720

State

image-20200703112935165

Vuex 使用 state 来存储应用中需要共享的状态。通过$store.state可以获得state中的状态值。

image-20200702202159244

Getters

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

Getter 接受 state 作为其第一个参数

image-20200702203658492

Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值

image-20200702203730221

image-20200702203747102

查看页面效果

GIF2039

state添加新的数组属性students。属性值为学生信息

image-20200703091655295

如果要显示年龄大于20的学生信息,可以使用Js的filter方法

image-20200703091824810

Getter 也可以接受其他 getter 作为第二个参数

image-20200703092044301

getters默认是不能传递参数的, 如果希望传递参数, 你可以通过让 getter 返回一个函数,来实现给 getter 传参。

image-20200703092258132

image-20200703092351027

查看页面效果

image-20200703092611560

mutations

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

Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state作为第一个参数

状态更新

mutation的定义方式

image-20200703095206364

通过mutation更新

image-20200703095229274

提交载荷

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

image-20200703100042990

image-20200703100147604

在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读

添加mutations

image-20200703100504810

image-20200703100612397

查看页面效果

GIF1006

对象风格的提交方式

当使用对象风格的提交方式,整个对象都作为载荷传给 mutation 函数

image-20200703101814186

此时就不能直接获取count,而是通过对象属性获取count

image-20200703101912745

Mutation 需遵守 Vue 的响应规则

既然 Vuex 的 store 中的状态是响应式的,那么当我们变更状态时,监视状态的 Vue 组件也会自动更新。这也意味着 Vuex 中的 mutation 也需要与使用 Vue 一样遵守一些注意事项:

  1. 最好提前在你的 store 中初始化好所有所需属性。
  2. 当需要在对象上添加新属性时,你应该
  • 使用 Vue.set(obj, 'newProp', 123), 或者
  • 以新对象替换老对象。例如,利用对象展开运算符我们可以这样写:
1
state.obj = { ...state.obj, newProp: 123 }

image-20200703102131651

image-20200703102138779

查看下面代码的方式一和方式二。都可以让state中的属性是响应式的.

使用常量替代 Mutation 事件类型

我们来考虑下面的问题

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

如何避免上述的问题呢?

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

创建一个mutation-types.js, 并且在其中定义我们的常量

image-20200703104535762

在store中的index.js引入,修改代码,通过[常量名]定义事件类型type

image-20200703104756813

同样在App.vue引入,修改代码

image-20200703105101302

GIF1051

Mutation 必须是同步函数

通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.

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

比如我们之前的代码, 当执行更新时, devtools中会有如下信息: 图1

image-20200703105314860

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

image-20200703105421949

Actions

我们强调, 不要再Mutation中进行异步操作

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

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

让我们来注册一个简单的 action

image-20200703112051380

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

image-20200703112223177

查看效果

GIF1122

同样的, 也是支持传递payload,同mutatios一样

image-20200703112517594

image-20200703112534995

查看效果

GIF1125

context是什么?

  • context是和store对象具有相同方法和属性的对象.

  • 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.

  • 但是注意, 这里它们并不是同一个对象, 为什么呢? 我们后面学习Modules的时候, 再具体说.

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

  • 我们定义了actions, 然后又在actions中去进行commit, 这不是脱裤放屁吗?
  • 事实上并不是这样, 如果在Vuex中有异步操作, 那么我们就可以在actions中完成了.

前面我们学习ES6语法的时候说过, Promise经常用于异步操作

在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject.

image-20200703112754549

image-20200703112809900

Module

Module是模块的意思, 为什么在Vuex中我们要使用模块呢?

Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.

当应用变得非常复杂时,store对象就有可能变得相当臃肿.

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

image-20200703113117709

基本使用

image-20200703114215642

App.vue中,由于会把模块放到store中的state作为一个对象属性,所以要通过$store.state才能访问到

image-20200703114306964

image-20200703114412618

Module局部状态

我们在moduleA中添加state、mutations、getters

mutation和getters接收的第一个参数是局部状态对象

image-20200703114715568

image-20200703114722551

注意:虽然, 我们的doubleCount和increment都是定义在对象内部的.但是在调用的时候, 依然是通过this.$store来直接调用的.且不能与外部的store中定义的重名否则不知道调用哪个,调用顺序是先去store中找,在从各个模块中查找调用!

Actions的写法

actions的写法呢? 接收一个context参数对象,使用对象的结构获取相应参数

局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState

image-20200703115049766

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

image-20200703115102040

项目结构

Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块

image-20200703115307665

image-20200703115329913