认识VueX

Vuex官网地址

Vuex是做什么的?

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

  🔹 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

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


🔷 状态管理到底是什么?

  🔹 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。

  🔹 其实,你可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。

  🔹 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。

  🔹 那么,多个组件就可以共享这个对象中的所有变量属性.


Vuex就是为了提供这样一个在多个组件间共享状态的插件,而且是响应式的。


🔷 管理什么状态呢 ?

  🔹 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。

  🔹 比如用户的登录状态、用户名称、头像、地理位置信息等等。

  🔹 比如商品的收藏、购物车中的物品等等。

  🔹 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的


✔ 单页面的状态管理———————————————————————————————


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

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

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

  🔹 State:不用多说,就是我们的状态。(你姑且可以当做就是data中的属性)

  🔹 View:视图层,可以针对State的变化,显示不同的信息。(这个好理解吧?)

  🔹 Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。


举个简单的例子:

我们来实现这样的一个简单的小案例。

🔷 在这个案例中,我们有木有状态需要管理呢?没错,就是个数counter。

🔷 counter需要某种方式被记录下来,也就是我们的State。

🔷 counter目前的值需要被显示在界面中,也就是我们的View部分。

🔷 界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是我们的Actions

这不就是上面的流程图了吗?


✔ 多状态管理———————————————————————————————


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

  🔹 多个试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)

  🔹 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态)

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

  🔹 状态1/状态2/状态3你放在自己的房间中,你自己管理自己用,没问题。

  🔹 但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理!!!

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

🔷 全局单例模式(大管家)

  🔹 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。

  🔹 之后,你们每个试图,按照我规定好的规定,进行访问和修改等操作。

这就是Vuex背后的基本思想。


✔ Vuex状态管理图例—————————-


当我们去修改Vuex 中的 State的时候,我们不应该直接去修改 State ,而是通过 Actions、Mutations 进而来修改State。

Devtools:它是Vue开发的一个浏览器插件,通过Mutations,它可以记录我们每一次修改State。

Actions:如果我们请求是异步操作的话,我们会通过Actions,将异步转化为同步,因为Devtools通过Mutations来监听我们对 State 的修改只能是同步操作。如果请求是同步的话,我们可以绕过Actions。


Vuex插件的安装

安装生产时依赖:

1
cnpm install vuex --save

Vuex 基本使用

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

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

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

   🔹 在index.js文件中写入如下代码:


✔ 挂载到Vue实例中

🔷其次,我们让所有的Vue组件都可以使用这个store对象

   🔹 来到main.js文件,导入store对象,并且放在new Vue中

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

挂载到Vue实例中 相当于执行 Vue.prototype.$store = store ,将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到。


✔ 使用Vuex


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

  🔹 1.提取出一个公共的store对象,用于保存在多个组件中共享的状态。

  🔹 2.将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到

  🔹 3.在其他组件中使用store对象中保存的状态即可

     通过this.$store.state.属性的方式来访问状态

     通过this.$store.commit('mutation中方法')来修改状态


🔷注意事项:

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

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


所以 可以通过浏览器插件Devtools来跟踪 state 的状态。


Vuex核心概念


State

🔷Vuex提出使用单一状态树, 什么是单一状态树呢?

  🔹 英文名称是Single Source of Truth,也可以翻译成单一数据源。

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

  🔹 OK,我用一个生活中的例子做一个简单的类比。

  🔹 我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。

  🔹 这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。

  🔹 这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。


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

  🔹 如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。

  🔹 所以Vuex也使用了单一状态树来管理应用层级的全部状态。

  🔹 单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。


Getters

有时候,我们需要从Stare中获取的一些经过改变的数据,我们就可以使用Getters。这比较类似于计算属性

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

举个简单的例子:在State中有一个数组,包含一些学生的基本信息。我们需要拿到age 大于18 的学生的信息,我们就可以使用Getters。


使用getters <h2>{{$store.getters.more18age}}</h2>

就获取到了age 大于18 的学生信息。



要是放在以前,我们可能会使用computed属性。

但是也有一个缺点,比如我们想要在多个组件中都同样的数据变换,那么我们都在在各种的组件中设置同样的computed,这样很不好。我们就可以使用vuex里的gitters属性。




我们在提一个需求:要得到年纪大于18 的学生的人数。我们就可以这样来做。

使用该函数:$store.getters.more18ageLength


我们再来看一个需求,我们不要把age写死,而是当做参数传递进来。

首先我们想到 的是直接在moreage中传递参数,但是这样是不行的,我们可以return 一个函数,在这个函数中传入参数,这样我们在使用的时候就可以,$store.getters.moreage(20)


总结:

🔹 如果我们没有传递参数的时候,我们就可以在getters中直接定义一个函数。

🔹 如果我们需要使用我们在getters中定义过的属性,我们就可以在定义函数的时候,将getters做为第二个参数传递进去。

🔹 如果我们需要在函数中传递参数,只能让getters本身返回另一个函数。参数定义在这个函数中,在使用是时候就可以传递参数了。



通过属性访问

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

1
store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

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

1
2
3
4
5
6
7
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
store.getters.doneTodosCount // -> 1

我们可以很容易地在任何组件中使用它:

1
2
3
4
5
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
}
}

注意,getter 在通过属性访问时是作为 Vue 的响应式系统的一部分缓存其中的。


通过方法访问

你也可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。

1
2
3
4
5
6
7
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

注意,getter 在通过方法访问时,每次都会去进行调用,而不会缓存结果。


mapGetters 辅助函数

mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { mapGetters } from 'vuex'

export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}

如果你想将一个 getter 属性另取一个名字,使用对象形式:

1
2
3
4
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})


Mutation

Mutation状态更新

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

🔷Mutation主要包括两部分:

  🔹 字符串的事件类型(type)

  🔹 一个回调函数(handler),该回调函数的第一个参数就是state

mutation的定义方式:

通过mutation更新:


Mutation传递参数

在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数

参数被称为是mutation的载荷(Payload)

比如我们提这样的一个需求:

点击按钮 可以+5 和+10.

设置鼠标点击事件,并将参数,放到commit 的第二个参数的位置。第一个参数是事件类型(type)

muation 中的代码:


但是如果参数不止一个 ,这个时候我们就要使用对象的形式来传递 ,也就是payload是一个对象。用的时候在取出需要的信息。

比如我们添加一个学生的信息。


Mutation 的提交风格

上面的通过commit进行提交是一种普通的方式。

Vue还提供了另外一种风格, 它是一个包含type属性的对象。

Mutation中的处理方式是将整个commit的对象作为payload使用。

我们将它打印一下,他就是整个commit的对象

所以我们使用的时候 我们将count取出来 就行了,代码没有改变, 依然如下:


Mutation响应式规则

Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新。

这就要求我们必须遵守一些Vuex对应的规则:

  1、提前在store中初始化好所需的属性.

  2、当给state中的对象添加新属性时, 使用下面的方式:

    方式一: 使用Vue.set(obj, ‘newProp’, 123)

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

我们来看一个例子:

那我们来对info添加一些属性。比如heigth。

当我们点击按钮的时候,虽然可以添加到state的info中,但是页面没有发生变化,这样添加无法做到响应式。

我们可以使用Vue.set(obj, 'newProp', 123) 的方式。

这样我们当点击按钮的时候,不仅state中的info会发生改变,页面中也会发生响应式的变化。


这是添加属性,当然删除属性也是一样的。

我们要做到响应式 还是要使用Vue.delete(state.info,"age")


Mutation常量类型

🔷 我们来考虑下面的问题:

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

  🔹 当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.

  🔹 方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.


🔷 如何避免上述的问题呢?

  🔹 在各种Flux实现中, 一种很常见的方案就是使用常量替代Mutation事件的类型

  🔹 我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个app所有的事件类型一目了然.


🔷 具体怎么做呢?

  🔹 我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量.

  🔹 定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.



Action

Mutation同步函数

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

  🔹 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.

  🔹 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.

举个简单的例子,我们这还是使用setTimeout() 来模拟异步请求。

当我们点击按钮的时候,3秒时候的确页面发生了修改,但是我们DevTools工具的中的name并没有修改。这是因为我们在Mutation中使用了异步函数。

这样的话,当我们去用Devtools改bug的时候,就会很蒙,不知道到底哪一个是对的。

这个时候我们就要使用我们的Action


Action基本定义

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

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

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

Action的基本使用代码如下:


当我们提交的时候,这里就不用commit了,就要使用 dispatch。dipatch用于提交Actions,commit用于提交Mutation。


Action参数的传递

当然我们也可以携带一些参数,也可以添加一个回调函数,告诉外界我们的异步操作已经完成了。


Action返回的Promise

上面回调操作是可以的,但是不够优雅。我们可以使用Promise。Promise经常用于异步操作.

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

return 一个new Promise 对象的时候。相当于把我们new 的Promise对象,替换了 this.$store.dispatch("amodify", "我是modity的参数") ,在它后面调用then() 方法。就可以继续使用我们的promise了。


在组件中分发 Action

你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { mapActions } from 'vuex'

export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}

组合 Action

Action 通常是异步的,那么如何知道 action 什么时候结束呢?更重要的是,我们如何才能组合多个 action,以处理更加复杂的异步流程?

首先,你需要明白 store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise:

1
2
3
4
5
6
7
8
9
10
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}

现在你可以:

1
2
3
store.dispatch('actionA').then(() => {
// ...
})

在另外一个 action 中也可以:

1
2
3
4
5
6
7
8
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}

最后,如果我们利用 async / await,我们可以如下组合 action:

1
2
3
4
5
6
7
8
9
10
11
// 假设 getData() 和 getOtherData() 返回的是 Promise

actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}

一个 store.dispatch 在不同模块中可以触发多个 action 函数。在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。


Module


认识Module

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

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

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

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


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


Moudle局部状态

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


✔ 模块中的 state————————————————————————————————–


模块中的state是这样定义的:

那我们怎样使用呐?$store.state.a.name


为什么要这么使用$store.state.a.name 呐,这是因为,最终模块a会放在 store中的state中去。


✔ 模块中的 Mutation————————————————————————————————–


定义和之前一样:

在使用的时候,和之前使用的一样,直接commit 就可以了。this.$store.commit('updateName', "灰太狼")


✔ 模块中的 Getters————————————————————————————————–


基本使用 和之前的一样。


如果我们想获取到 store (大模块)中的state的数据的话,我们可以这样使用。

在模块中,getters的函数可以有第三个参数,rootState 。它就代表了大模块中的state。


✔ 模块中的 Action————————————————————————————————–


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

actions 中的 context.commit 不和之前的一样,它仅仅提交的是自己模块中的 mutations。

我们来看一下 模块a中的context到底是什么。我们来打印一下。

所以,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState,如果getters中也需要使用全局的状态, 可以接受更多的参数。


项目结构

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


补充:对变量名的管理