vuex详解
一、认识vuex
1、vuex是什么
官方解释:Vuex 是一个专为 Vue.js 应用程序开发提供的状态管理模式。
- 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
状态管理到底是什么?
- 状态管理模式、集中式存储管理这些名词听起来就非常高大上,让人捉摸不透。
- 其实,可以简单的将其看成把需要多个组件共享的变量全部存储在一个对象里面。
- 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用。
- 那么,多个组件是不是就可以共享这个对象中的所有变量属性了。
其实这一切都可以通过自己定义一个对象来实现的,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
- 答案当然是可以的,只是我们要先想想VueJS带给我们最大的便利是什么呢?就是响应式开发。
- 如果你自己封装实现一个对象来保证它里面所有的属性做到响应式是非常麻烦的。
- 所以如果要做到响应式开发,又能够共享的话,用vuex来管理这些对象是非常方便有效的。
总结
简单来说就是:多个组件里面都有变量需要对外共享,这个时候这个变量放在哪个组件内都是不合适的。把这多个需要共享的变量都放在一个对象里面去,这样就可以相互使用,非常方便了。如果这个对象是用vuex进行管理的话,那么这里面的变量都是响应式的
2、管理什么状态
有什么状态时需要我们在多个组件间共享的呢?
- 如果你做过大型开放,你一定遇到过多个状态,在多个界面间的共享问题。
- 比如用户的登录状态(token)、用户名称、头像、地理位置信息等等。
- 比如商品的收藏、购物车中的物品(每个界面都可以加入购物车,有个购物车页面进行专门管理)等等。
- 这些状态信息,我们都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的。
我们先来看看单界面的状态管理是怎么样的。
3、单页面的状态管理
我们知道,要在单个组件中进行状态管理是一件非常简单的事情。什么意思呢?我们来看下面的图片。
这图片中的三种东西,怎么理解呢?
State:驱动应用的数据源,就是我们的状态。(什么是状态,其实就是变量,可以当做就是data中的属性)
View:视图层,可以针对State的变化,显示不同的信息。(以声明方式将 state 映射到视图)
Actions:这里的Actions主要是用户的各种操作:点击、输入等等,会导致状态的改变。(响应在 view 上的用户输入导致的状态变化)
写点代码通过单界面状态管理的实现,加深理解:
在这个案例中,counter这个变量就是使用了状态管理。counter需要某种方式被记录下来,也就是我们的State。counter目前的值需要被显示在界面中,也就是我们的View部分。界面发生某些操作时(我们这里是用户的点击,也可以是用户的input),需要去更新状态,也就是我们的Actions。
但是这仅仅是单个页面的状态管理,多页面的状态管理就比较复杂了。
4、多页面状态管理
Vue已经帮我们做好了单个界面的状态管理,但是如果是多个界面呢?
- 多个页面试图都依赖同一个状态(一个状态改了,多个界面需要进行更新)
- 不同界面的Actions都想修改同一个状态(Home.vue需要修改,Profile.vue也需要修改这个状态),也就是说对于某些状态(状态1/状态2/状态3)来说只属于我们某一个试图,但是也有一些状态(状态a/状态b/状态c)属于多个试图共同想要维护的
- 比如说状态1/状态2/状态3你放在自己的房间中,你自己管理自己用。但是状态a/状态b/状态c我们希望交给一个大管家来统一帮助我们管理,这就需要Vuex为我们提供这个大管家的工具。
全局单例模式(大管家)
- 我们现在要做的就是将共享的状态抽取出来,交给我们的大管家,统一进行管理。
- 之后,每个视图,按照我规定好的规定,进行访问和修改等操作。
这就是Vuex背后的基本思想。
小贴士
要想使用vuex,首先需要像使用vue-router一样,先安装插件:
- 利用npm install vuex --save安装vuex插件
- 然后在用Vue.use()使用它。(如果在main.js里面使用import引入,use导入插件,在写一大堆代码进行管理共享对象的话,main.js的文件就会非常的大,每个具体的业务处理写在入口函数里面也很不好,因此可以像vue-router一样,单独创建一个文件来管理这些需要共享的状态,这里一般取名为store,就是仓库的意思,多个页面需要使用共享对象的时候,就从仓库里面去拿)
实现的步骤(思路):
- 利用npm install vuex --save安装vuex插件
- 在src下创建一个store文件夹进行共享对象的管理,并创建一个index.js,并加入下面的代码:
index.js:
- 然后在入口文件(main.js)引入并挂载
- 挂载在vue实例后,vue内部就会通过该命令Vue.prototype.$store=store,将$store添加到Vue.prototype,到时候在其他组件上就可以通过$store去拿到store,并且去使用它
通过代码理解一下:
说明一下需求,就是两个页面共享counter,在App.vue中通过按钮加减数字,HelloVuex.vue中的counter也会改变
App.vue中:
<template>
<div id="app">
<h1>---------App内容---------</h1>
<h2>{{message}}</h2>
<h2>{{$store.state.counter}}</h2>
<button @click="$store.state.counter++">+</button>
<button @click="$store.state.counter--">-</button>
<br>
<h1>-----------HelloVuex内容------------</h1>
<hello-vuex></hello-vuex>
</div>
</template>
<script>
import HelloVuex from "./components/HelloVuex";
export default {
name: 'App',
data() {
return {
message: '我是App组件',
}
},
components: {
HelloVuex
}
}
</script>
<style>
</style>
HellVuex.vue中:
<template>
<div class="hello">
<h1>{{ $store.state.counter }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloVuex'
}
</script>
<style scoped>
</style>
main.js:
import Vue from 'vue'
import App from './App'
import store from './store'
Vue.config.productionTip = false
Vue.prototype.$store=store
new Vue({
el: '#app',
store,
render: h => h(App)
})
index.js:
import Vue from 'vue'
import Vuex from 'vuex'
//1.安装插件,一旦我们是用use,那么底层就去执行底层的install方法
Vue.use(Vuex)
//2.创建一个对象
const store=new Vuex.Store({
state:{
counter: 1000
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
}
})
//3.导出store共享
export default store
实现结果:
5、vuex状态管理图例
-Devtools是vue开发的一个浏览器插件,插件可以帮我们记录每一次修改state的状态
- vue组件是可以直接拿到state的,但是不能直接去修改state,必须按照上图的方式去修改。其实,也是可以直接通过Vue.Components去Mutations去修改state的。这样的话,Acitons是用来干嘛的?
- Actions就是为了处理异步操作,因为Mutations里如果有异步操作的话,Devtools是跟踪不到异步操作的。有异步操作的时候,一定到在Actions里面处理异步操作,处理完后提交给Mutations,这时候Mutations里面就是同步操作了。我们可以看到Actions连接着后端的API。
安装Devtools:
- 谷歌浏览器右上角三个点
- 更多工具-扩展程序-点击左上角的扩展程序-打开chrome应用店-搜索Devtools
- 选择下图安装即可
- 在安装完这个浏览器插件后,要重启浏览器才有效
二、vuex基本使用
简单案例
还是通过实现之前的案例认识如何使用vuex:
-
首先,我们需要在某个地方存放我们的Vuex代码:
-
这里,我们先创建一个文件夹store,并且在其中创建一个index.js文件
-
在index.js文件中写入如下代码:
-
其次,我们让所有的Vue组件都可以使用这个store对象。到main.js文件,导入store对象,并且放在new Vue中。这样,在其他Vue组件中,我们就可以通过this.$store的方式,获取到这个store对象了。
-
使用Vuex的count
以上就是使用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核心概念
Vuex有几个比较核心的概念:
- State
- Getters
- Mutation
- Action
- Module
1、State单一状态树
Vuex提出使用单一状态树, 什么是单一状态树呢?
英文名称是Single Source of Truth,也可以翻译成单一数据源。
单一数据源是什么?我们来看一个生活中的例子。
- 我们知道,在国内我们有很多的信息需要被记录,比如上学时的个人档案,工作后的社保记录,公积金记录,结婚后的婚姻信息,以及其他相关的户口、医疗、文凭、房产记录等等(还有很多信息)。
- 这些信息被分散在很多地方进行管理,有一天你需要办某个业务时(比如入户某个城市),你会发现你需要到各个对应的工作地点去打印、盖章各种资料信息,最后到一个地方提交证明你的信息无误。
- 这种保存信息的方案,不仅仅低效,而且不方便管理,以及日后的维护也是一个庞大的工作(需要大量的各个部门的人力来维护,当然国家目前已经在完善我们的这个系统了)。
- 这个和我们在应用开发中比较类似:
如果你的状态信息是保存到多个Store对象中的,那么之后的管理和维护等等都会变得特别困难。所以Vuex也使用了单一状态树来管理应用层级的全部状态(所有的状态放到一个store来管理和维护)。单一状态树能够让我们最直接的方式找到某个状态的片段(因为只有一个store),而且在之后的维护和调试过程中,也可以非常方便的管理和维护。
2、Getters
getters在开发中用的比较多,相当于单个组件里面的计算属性,计算属性什么时候用?当我们的某个数据需要经过一系列变化之后在在页面中使用的时候,就会用到计算属性。getters也是如此。
有时候,我们需要从store中获取一些state变化后的状态,那就需要使用getters来实现。
需求一:比如下面的Store中获取学生年龄大于20的个数:
1.现在index.js文件中添加下面代码:
在Store中定义getters
然后在App.vue和HellowVuex.vue中引用:
总结:类似于上面要取store里面的某个数据要经过一系列处理后,在给其他页面进行使用的话,就需要用到getters。
需求二:不仅要拿到年龄大于20的学生,并且还要拿到年龄大于20的个数,如果直接使用$store.getters.more20stu.length来实现的话,非常的简答,这里要求利用上个需求的getters来实现。
需求三:获取年龄大于age,age不写死(在用getters的时候传进来的)
注意
getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回一个函数。
也可以使用箭头函数来实现:
结果是一样的。
3、Mutation状态更新
Vuex的store状态的更新唯一方式:提交Mutation
Mutation主要包括两部分:
- 字符串的事件类型(type)
- 一个回调函数(handler),该回调函数的第一个参数就是state。
mutation的定义方式:
通过mutation更新:
4、Mutation传递参数
在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数,该参数被称为是mutation的载荷(Payload)
Mutation中携带参数的代码:
App.vue中的代码:
但是如果参数不是一个呢?比如我们有很多参数需要传递,这个时候, 我们通常会以对象的形式传递, 也就是payload是一个对象。这时可再从对象中取出相关的信息。
比如说,添加一组学生信息:
5、Mutation提交风格
上面介绍的通过commit的方法进行提交是一种普通的方式,Vue还提供了另外一种风格, 它是一个包含type属性的对象。
点击+5按钮后同样实现+5的操作
6、Mutation响应规则
Vuex的store中的state是响应式的, 当state中的数据发生改变时, Vue组件会自动更新。因为,store里面定义的东西一开始就加进响应式系统里面了,而响应式系统会监听属性的变化,当属性发生变化时,会通知所有界面中用到该属性的地方,让界面刷新。这就要求必须遵守一些Vuex对应的规则:
- 提前在store中初始化好所需的属性
- 当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, ‘newProp’, 123)
方式二: 用新对象给旧对象重新赋值
我们来看一个例子:
App.vue中:
上面的代码可以成功显示吗?答案肯定是不可以的。因为上面已经说了,需要提前在store中初始化好所需的属性,这个新添加的属性address是没有在store里面初始化的,因此页面是没有刷新的,当已经是添加进info里面了,我们可以通过浏览器的插件看一下。
如果这种情况也要实现响应式呢?这里就需要上面介绍的vuex对应的规则里面的两种方式:
- 当给state中的对象添加新属性时, 使用下面的方式:
方式一: 使用Vue.set(obj, key/number, 值)----->里面有三个参数,一个参数为对象/数组;第二个参数如果第一个参数是对象的话,那么这里是key,如果是数组的话,就是number;第三个参数就是值
方式二: 用新对象给旧对象重新赋值
方法一:
如果要删除某个属性呢?如果通过js原生的方式去删除,也就是说通过delete state.info.age这行代码去删除也是不能实现响应式的。可通过Vue.delete(对象,key)该方法去删除,便可实现响应式。
方法二:以新对象替换老对象
点击完修改信息的按钮后,会将信息直接添加在后面,实现响应式。
7、Mutation常量类型
我们来考虑下面的问题:
在mutation中, 我们定义了很多事件类型(也就是其中的方法名称)。当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多。方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况。
如何避免上述的问题?
一种很常见的方案就是使用常量替代Mutation事件的类型。我们可以将这些常量放在一个单独的文件中, 方便管理以及让整个App.vue中所有的事件类型一目了然。
具体怎么做?
我们可以创建一个文件: mutation-types.js, 并且在其中定义我们的常量。定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称。
8、Mutation同步函数
通常情况下, Vuex要求我们Mutation中的方法必须是同步方法。
- 主要的原因是当我们使用devtools时, devtools可以帮助我们捕捉mutation的快照。有什么用呢?简单来说,就是当我们的程序没有实现我们想要的效果使,devtools有助于我们跟踪数据的变化,用来调试程序。
- 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成。异步操作在mutations中也是可以实现页面刷新的,但是devtools里面的数据就不会更改,这样就不能追踪数据的变化了。
从结果可以看到,如果在异步操作中修改属性,页面是可以显示的,但是devtools是不会改变的。通常情况下, 不要再mutation中进行异步的操作。
9、Action
**我们强调, 不要在Mutation中进行异步操作。**但是某些情况, 我们确实希望在Vuex中进行一些异步操作, 比如网络请求, 必然是异步的。 这个时候怎么处理呢?这个时候就可以用Action来处理,Action类似于Mutation, 但是可用来代替Mutation进行异步操作。
Action的基本使用代码如下:
- 在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch。同样的, 也是支持传递payload。
- context是和store对象具有相同方法和属性的对象。
- 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等。
- 但是注意, 这里它们并不是同一个对象。我们后面学习Modules的时候, 再具体介绍。
前面我们学习ES6语法的时候说过, Promise经常用于异步操作。在Action中, 我们可以将异步操作放在一个Promise中, 并且在成功或者失败后, 调用对应的resolve或reject。
10、Modules
Module是模块的意思, 当在一个项目中有很多变量需要进行共享时,就要把这些共享的对象写到state里面去。而且官方规定Vue使用单一状态树,那么也意味着很多状态都将交给Vuex来管理,不能写成多个store。当应用变得非常复杂时,store对象就有可能变得相当臃肿。为了解决这个问题, Vuex允许我们将store分割成模块,即Module, 而每个模块拥有自己的state、mutations、actions、getters等。
注意:
modules中定义的对象会被自动放到state里面去,modules中的state这些属于局部状态,store里面state等属于全局状态。
modules中的mutations,在modules中定义的事件类型的名字不要和store里面的mutations的事件类型名字一样,因为提交的时候是从store的mutations里面找的,没有找到在到modules里面的mutations找。
modules中的getters,也是同样如此。**注意:**虽然这里面的fullName是在对象里面定义的,但仍可以通过this.$store来直接调用。因为在store中没有找到,会到modules中去找。
modules中的actions,actions里面就是进行异步操作的。**注意:**这里的context.commit不再是store里面的mutations了,这里的context是modules里面的mutations,因为context的意思就是上下文。
我们可以把context打印一下。看看里面有什么。
可以看到,我们在这里面,可以通过context去使用全局状态里面的东西。
小贴士
Actions的另一种写法:
可能会有疑问,这里面不是context吗,为什么是{state,commit,rootState}也可以实现?因为,这个语法是es6对象解构的语法,context其实是一个对象,通过上面打印出来的context我们可知context里面是有state,commit,rootState等属性的,因此这里可直接用个对象拿到需要用到的context里面的一些属性来使用。
四、项目结构组织
当我们的Vuex帮助我们管理过多的内容时, 好的项目结构可以让我们的代码更加清晰。像上面的代码中,store中state、getters这些代码都是放在一个index.js文件里面的,当vuex管理的内容非常多时,显然这种方式查找代码非常的困难,也难以分清层级之间的关系。因此,为了有一个好的项目结构,我们可将store里面的不同属性分离出去。
像这样,把集成在index文件里面的所有代码都抽离成这样的单独的文件,那么后续在大型项目开发中,就非常方便的查阅、更改代码了。