Vuex 入门指南
1.Vuex是什么?
我们还是像以往一样先看一看官方文档对此的解读(Vuex 是什么? · GitBook)
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
是不是又遇到了很多看起来很高大上听起来却一脸懵逼的专业术语?别急别急,我们慢慢来剖析一下这个Vuex究竟是个啥东西,他能做些啥。
2.Vuex到底用来做什么?
用通俗一点的话来说,Vuex就是一个用于管理SPA项目(不知道SPA是什么?请参考本专栏代码之美 - 知乎专栏中的历史文章)中状态的开源产品。
接下来又引出了一个问题,什么是状态,为什么要用Vuex这个东西去管理它?
3.什么是状态?为什么要去管理它?
状态这个东西其实我们生活中随处可见。我们头顶上的灯就有两种状态,一种是开,一种是关。状态说白了就是灯这个对象的的某个属性的值。
如果你对状态和属性这两个概念还是有所不了解,那么我打一个其他的比方吧。
我们平时是否有玩过王者荣耀或者英雄联盟LOL之类的网游?这些游戏里面每一个英雄当前都有生命值,法力值,攻击力,法术强度,护甲和魔抗等等,这些是这个英雄的属性,也就是英雄这个对象当前的状态。
属性分为固定属性和可变属性,一般像LOL里面大部分ADC英雄如果没有特殊的被动或者其他装备的支持,那么它的的攻击距离都是固定的,这个就是固定属性,这种固定属性的状态由于正常情况下都是不变的,所以我们可以直接写死在代码中(这种写死在代码中的变量的值称为硬编码),但是像其他的攻击力法术强度等等都是随装备和等级变化,那么这种属性是可变属性。
这些属性的状态由于会根据用户的各种操作(比如说出装备,打怪升级升级)变化。在传统的Vue.js的组件化开发中,一般这些状态都是分散在各个组件中,此时此刻如果两个英雄互相打起来了,那么就得分别去不同的组件中取状态值,然后进行状态值的修改,最后还要互相读取对方的状态值。如果他们本身是父子组件,那么还可以通过事件触发或者Prop属性来传递状态,但是如果是不同的组件,由于由于Vue.js本身组件之间有作用域,它们无法直接相互通信,所以就需要一些东西例如Vuex去集中管理和追踪它的变化。(如果你现在还是不明白这一大段话,可以好好回顾一下官方文档中组件 - vue.js非父子组件通信 这个部分的内容)
在游戏中,这些状态一般以变量的形式保存在内存中,但是由于用户玩游戏的时候并不是直接去使用内存管理工具查看他们在内存里面的值,而是通过游戏界面去看这些值,所以还需要像Vue.js这种MVVM框架将状态同步到视图中。这就是Vue.js和Vuex之间的关系了。
4.什么情况下我应该使用 Vuex?
官方文档(Vuex 是什么? · GitBook)中说:
虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 global event bus 就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
他的意思其实就是如果开发的程序并不是很庞大,一个页面中的组件不是很多并且他们之间并不需要大量频繁的互相读写操作,那么就可以直接使用传统的Vue.js中的组件Prop或者事件触发来修改状态,或者用组件 - vue.js#非父子组件通信 中介绍的new一个空的Vue对象实例,并且通过事件触发等方式来跨组件通信。
否则的话还是建议使用Vuex。虽然Vuex本身需要有一段时间的学习成本,但是这个学习成本相对于你开发时期使用传统非父子组件通信机制遇到的各种坑来说还是比较划算的。这个就看你自己的权衡了。
5.Vuex怎么安装和使用?
在前面讲解Vue.js入门的时候,我们用的是Vue-Cli这个脚手架工具来搭建的,由于这个脚手架工具本身会帮我们配置好npm的package.json文件,这个文件里面包含了这个Vue.js项目中所有依赖的包。
但是默认情况下这个脚手架工具没有为我们将Vuex这个依赖包给包含进去,所以我们得自己去“声明”一下我们这个Vue.js项目中需要依赖Vuex这个包。
我们该怎么“声明”呢?现在有两种办法:
一种是直接修改package.json,这种方法看起来有点复杂,很多新手怕一不小心修改出错,可能会导致整个package.json文件结构出错,影响以后项目的依赖安装。
还有一种方法比较安全,只需要一行命令:
npm install vuex --save
表示安装vuex这个包,--save表示将这个依赖包与本项目的依赖关系写入package.json中。
然后我们仅仅安装了这个依赖包是没有用的,我们还得在之前Vue-Cli为我们自动构建好的项目文件中的main.js主入口文件的开头里面加上两行这样的代码:
import Vuex from 'vuex'
Vue.use(Vuex)
第一行是用ECMAScript6的import将vuex包导入进来(这个是不是和java中导入jar包以及php中导入命名空间很相似?)
第二行是Vue.js本身的插件注入语法(参考官方文档插件 - vue.js),将插件注入Vue.js的目的是方便我们在插件内部调用它。
6.官方文档的五大核心概念是什么?
打开官方文档(Introduction · GitBook)能看到五大核心概念,他们都是啥?看了半天官方文档我还是对它们没什么了解,楼主能不能以通俗易懂的方式讲解一下它们的作用?当然可以啦!
首先先看一遍这个代码,不需要你看懂它。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
7.State(状态)
官方文档:State · GitBook
这个状态就是前面所介绍的“状态”值的存放处。
看第6节末尾的代码中,状态就是在state属性中以键值对的形式声明这个SPA中所有的状态。上面的代码中声明了一个count状态。
之所以要在这里声明所有状态的原因,一是让代码更加优雅,如果你接手你同事的项目的时候,能够一眼从Vuex的状态声明中看出这个应用中有哪些状态,肯定开发效率杠杠滴。二是如果在这里声明了状态,那么Vuex就能够追踪到这个状态的变化。那么Vue.js中就可以在视图中对这个状态做出响应。
读取状态当然也是直接读取这个属性里面的各种子属性了。
8.Getter(获取器)
官方文档:Getters · GitBook
这个获取器和一些后端开发中模型层ORM中的获取器其实是差不多的功能。
比如说后端返回给我们的是一个int类型的时间戳,我们想把这个时间戳转换成正常人类可读的文本型时间表现形式(比如说2017年3月11日 12:43:31),那么我们就得在所有获取该状态的代码中增加一个转换函数。
可是现在有了状态获取器之后,我们可以统一将这个时间戳转字符串的函数写在获取器里面,要调用的时候就直接调用获取器就好了。
还有一些其他场景也可以使用获取器,比如说像错误码这种东西一般都是一个数字码对应一个文字形式的错误原因,我们也可以通过获取器来实现通过错误码拉取错误原因的功能。
使用获取器的方法则是直接调用Vuex实例的getter下的各种函数即可。
9.Mutations(转变)
官方文档:Mutations · GitBook
这个Mutations其实国内目前也没有比较好的翻译,通常我们都是直接称Mutations。
我们前面只讲了可以通过调用Vuex的实例的state属性或者getter获取器来读取状态。但是没讲到如何修改状态。
官方文档中已经讲了需要先在Vuex实例的Mutations下编写对应的修改函数来修改状态。并且要修改的时候,要通过Vuex实例的commit方法来提交修改。也就是说任何对state状态的修改操作都必须写在Mutations中,并且还得用Vuex实例的commit来提交修改操作,并且由于Mutations函数可以传入参数,所以commit同理也可以传入参数。
这个时候可能有一些同学就会提问了,前面既然讲到了读取可以直接访问Vuex实例的state属性,为什么修改却不能直接去操作Vuex实例的state呢?官方文档和其他高手的回答是:
再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。
相当于我们通过一个Mutations函数可以显式的在代码中告诉开发者,我们这个SPA中究竟会对状态进行哪些操作,操作方式是什么。并且在后期我们使用一些辅助开发工具,可以保存状态的快照,就像git或者svn一样可以回滚状态。如果你还是有点不明白,总之你就按照官方文档说的去做吧,等开发一段时间之后会慢慢明白作者的良苦用心的,哈哈。
还有一个问题就是为什么状态修改的提交必须通过Vuex实例的commit方法提交呢?为什么不能直接调用Mutations函数呢?除了上面官方文档中提到的原因,网上还有高手解释了:因为Vue.js的状态修改其实是在内部有一个修改队列,通过commit的方式提交修改可以保证状态的修改是有序的。
10.Actions(动作)
官方文档:Actions · GitBook
前面提到了Mutations中可以对状态进行操作,但是忘记告诉各位同学,Mutations中对状态的操作只能是同步操作,不能是异步操作。
如果这个时候我们有一个对状态的修改操作是异步的怎么办呢?
首先看看什么是异步操作?比如说ajax就可以选择是否发起异步请求,发起异步请求之后,我们就需要在回调函数里面进行请求结果的处理。关于JavaScript异步的知识大家可以先使用各种搜索引擎自学一下。
现在回到actions上来,看看官方文档(Actions · GitBook):
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
其实异步的状态修改本质上还是通过几个同步操作组合的,所以我们还是得先声明好mutation同步操作方法,然后在actions中进行异步操作。如果我们暂时手头上没有ajax接口用于异步请求,那么我们可以像官方文档一样用setTimeout这种最简单的测试方法来理解。
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
前面讲到了mutation是用commit来提交操作的,那么actions是怎么提交的呢?官方文档中说了actions是使用Vuex实例的dispatch方法来提交(其实说分发会更加准确)的。
至于其他更详细的actions操作官方文档讲的还是比较清楚的,没有什么比较复杂的概念,可以参考官方文档学习,这里不做更多讲解。
至于后面“组合actions”中提到的Promise对象以及 async / await 都是JavaScript中的一些特性,大家可以利用搜索引擎进行更多了解。
11.Modules(模块)
官方文档:Modules · GitBook
如果你的SPA项目非常的庞大,那么状态可能本身还需要进行分模块分类管理,这个时候就需要用到模块了。官方文档中已经给出了比较详细的模块操作代码,这里不再做更多讲解。
至于前面在将actions的时候,官方文档中说actions方法在声明的时候需要带上一个context参数,原因在这里可以得到解答:
对于模块内部的 action,context.state 是局部状态,根节点的状态是 context.rootState
12.严格模式
官方文档:严格模式 · GitBook
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到。
前面提到了state的修改需要通过提交Mutations或者分发Actions,但是事实上我直接修改state可以吗?当然也是可以的,但是在开发阶段,为了尽可能防止开发者直接修改,就可以通过严格模式来检测这种错误的修改方式,并且抛出异常。
但是官方文档后面也提到了:
不要在发布环境下启用严格模式!严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失。
因此不要在生产环境下开启严格模式导致性能损失。
结语:
Vuex综合来看是一个非常适合在Vue.js中使用的状态管理工具,当然类似的状态管理工具也有很多,比如说React的Redux。
但是我们为了能够尽可能保证项目稳定性以及学习曲线的平滑,推荐在Vue.js中使用Vuex