一、什么是Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
这个状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将state映射到视图;
- actions,响应在view上的用户输入导致的状态变化。
以下是一个表示“单向数据流”理念的极简示意:
更复杂的情况
- 多个视图依赖于同一状态。
-
来自不同视图的行为需要变更同一状态。
二、什么情况下我应该使用 Vuex?
虽然 Vuex 可以帮助我们管理共享状态,但也附带了更多的概念和框架。这需要对短期和长期效益进行权衡。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的
global event bus
就足够您所需了。但是,如果您需要构建是一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。引用 Redux 的作者 Dan Abramov 的话说就是:
Flux 架构就像眼镜:您自会知道什么时候需要它。
三、安装
首先需要创建一个基于Vue的工程,通常你可以这样做:
# 全局安装 vue-cli
$ npm install --global vue-cli
# 创建一个基于 webpack 模板的新项目
$ vue init webpack my-project
# 安装依赖,走你
$ cd my-project
$ npm install
$ npm run dev
- 利用npm包管理工具,进行安装 vuex。在控制命令行中输入下边的命令就可以了。
npm install vuex --save
要注意的是这里一定要加上 –save,因为你这个包我们在生产环境中是要使用的。
通过简单的依赖安装操作,vuex就算引用成功了,接下来我们就可以尽情的玩耍了。
一个简单的示例,在main.js文件中输入如下代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
store.commit('increment')
/* eslint-disable no-new */
new Vue({
el: '#app',
template: `{{ count }}`,
computed: {
count () {
return store.state.count
}
}
})
运行后可以看到页面上显示一个1,即函数increment已经正确的执行,count由初始的0自加后变为1输出,
需要指出的是,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。
通常我们不会这样去使用它,更好的使用方法是:
- 新建一个store文件夹(这个不是必须的),并在文件夹下新建store.js文件,文件中引入我们的vue和vuex。
import Vue from 'vue';
import Vuex from 'vuex';
- 在store.js文件中,用Vue.use对vuex进行引用。
Vue.use(Vuex);
4.在main.js 中引入新建的vuex文件
import store from './store/store'
- Vuex 通过 store 选项,提供了一种机制将状态从根组件“注入”到每一个子组件中(需调用 Vue.use(Vuex),通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store 访问到。:
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/store'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
template: ' ',
components: { App }
})
四、使用
- 现在我们store.js文件里增加一个常量对象。
state: {
count: 1
},
- 用export default 封装代码,让外部可以引用。
export default new Vuex.Store({
name: 'store',
state: {
count: 1
}
})
- 新建一个Vue的测试模板,位置在components文件夹下,名字随便起,我这里叫count.vue。在模板中我们使用{{$store.state.count}}输出count 的值(注意:由于前面已经将store注入到vue的实例中,故不需要再次引用)。
{{msg}}
{{$store.state.count}}
- 在store.js文件中加入两个改变state的方法。
mutations: {
increment (state) {
state.count += 1
},
decrement (state) {
state.count -= 1
}
}
- 在test.vue模板中加入两个按钮,并调用mutations中的方法。
- 在router文件夹下的index.js中引用文件,定义对应的路由,运行程序并进入该界面,点击按钮查看效果
{
path: '/count',
name: 'Count',
component: Count
}
五、state访问状态对象
state,这个就是我们说的访问状态对象,它就是我们SPA(单页应用程序)中的共享值。
访问状态对象赋值给内部对象,也就是把stroe.js中的值,赋值给我们模板里data中的值。有三种赋值方式
- 通过computed的计算属性直接赋值
computed属性可以在输出前,对data中的值进行改变,我们就利用这种特性把store.js中的state值赋值给我们模板中的data值。
computed:{
count(){
return this.$store.state.count;
}
}
这里需要注意的是return this.$store.state.count这一句,一定要写this,要不你会找不到$store的。这种写法很好理解,但是写起来是比较麻烦的,那我们来看看第二种写法。
- 通过mapState的对象来赋值
我们首先要用import引入mapState。
import {mapState} from 'vuex'
然后还在computed计算属性里写如下代码:(根据需要选用一种方法即可)
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count, // es6写法,function (state) { return state.count }
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
- 当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
- 还可以采用简化写法(个人比较喜欢这种写法本质上和第三种是一个意思,后面的mutations中也有类似的写法)
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
num: 'count'
})
这个算是最简单的写法了,在实际项目开发当中也经常这样使用。
六、Mutations
我们已经定义并且拿到了state的值,那么如何去改变他们的状态呢?
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
- 修改状态
Vuex提供了commit方法来修改状态,前边我们在给按钮添加点击事件时已经使用过该方法了,代码如下。
store.js文件:
mutations: {
increment (state) {
state.count += 1
},
decrement (state) {
state.count -= 1
}
}
传值:这只是一个最简单的修改状态的操作,在实际项目中我们常常需要在修改状态时传值。比如上边的例子,是我们每次只加1,而现在我们要通过所传的值进行相加。其实我们只需要在Mutations里再加上一个参数,这个参数又称为mutation 的 载荷(payload),并在commit的时候传递就就可以了。我们来看具体代码:
现在store.js文件里给increment 方法加上一个参数n。
mutations: {
increment (state, n) {
state.count += n
},
decrement (state) {
state.count -= 1
}
}
在count.vue里修改按钮的commit( )方法传递的参数,我们传递10,意思就是每次加10。
在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
更推荐的写法:对象风格的提交方式
提交 mutation 的另一种方式是直接使用包含 type 属性的对象:
store.commit({
type: 'increment',
amount: 10
})
- 上面的写法还是复杂了点,有没有更简单的呢,答案是肯定的,模板获取Mutations方法
实际开发中我们也不喜欢看到$store.commit( )这样的方法出现,我们希望跟调用模板里的方法一样调用。
例如:@click=”reduce” 就和没引用vuex插件一样。要达到这种写法,只需要简单的两部就可以了:
1)、在模板count.vue里用import 引入我们的mapMutations:
import { mapState,mapMutations } from 'vuex';
2)、在模板的<\script>标签里添加methods属性,并加入mapMutations
methods:mapMutations([
'increment '
]),
通过上边两部,我们已经可以在模板中直接使用我们的reduce或者add方法了,就像下面这样。
同state一样,该写法也可以利用对象展开运算符进行简化
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
调用
七、getters计算过滤操作
getters从表面是获得的意思,可以把他看作在获取数据之前进行的一种再编辑,相当于对数据的一个过滤和加工。你可以把它看作store.js的计算属性。
- getters基本用法:
比如我们现在要对store.js文件中的count进行一个计算属性的操作,就是在它输出前,给它加上100.我们首先要在store.js里用const声明我们的getters属性。
getters: {
count: state => {
return (state.count += 100)
}
在store.js里的配置算是完成了,我们需要到模板页对computed进行配置。在vue 的构造器里边只能有一个computed属性,如果你写多个,只有最后一个computed属性可用,所以要对上节课写的computed属性进行一个改造。
...mapState({
name: 'name',
count () {
return this.$store.getters.count
}
})
使用
{{count}}
需要注意的是,你写了这个配置后,在每次count 的值发生变化的时候,都会进行加100的操作。
- 用mapGetters简化模板写法
我们都知道state和mutations都有map的引用方法把我们模板中的编码进行简化,我们的getters也是有的,我们来看一下代码。
首先用import引入我们的mapGetters
import { mapState, mapMutations, mapGetters } from 'vuex'
在computed属性中加入mapGetters
...mapGetters({
counted: 'count'
})
八、actions异步修改状态
actions和之前讲的Mutations功能基本一样,不同点是,actions是异步的改变state状态,而Mutations是同步改变状态。
- 在store.js中声明actions
actions是可以调用Mutations里的方法的,我们还是继续在上节课的代码基础上进行学习,在actions里调用increment和decrement两个方法。
actions: {
incrementAsync ({ commit }, payload) {
setTimeout(() => {
commit('increment', payload)
}, 1000)
},
decrementAction (context) {
context.commit('decrement')
}
}
在actions里写了两个方法incrementAction 和decrementAction ,在方法体里,我们都用commit调用了Mutations里边的方法。细心的小伙伴会发现这两个方法传递的参数也不一样。
- ontext:上下文对象,这里你可以理解成store本身。
- {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了。
- 模板中的使用
我们需要在count.vue模板中编写代码,让actions生效。我们先复制两个以前有的按钮,并改成我们的actions里的方法名,分别是:incrementAction 和decrementAction 。
改造一下我们的methods方法,首先还是用扩展运算符把mapMutations和mapActions加入。
methods:{
...mapActions({
incrementAsync: 'incrementAsync', // 将 `this.add()` 映射为 `this.$store.commit('increment')`
decrementAction: 'decrementAction'
})
},
你还要记得用import把我们的mapActions引入才可以使用。
九、module模块组
随着项目的复杂性增加,我们共享的状态越来越多,这时候我们就需要把我们状态的各种操作进行一个分组,分组后再进行按组编写。那今天我们就学习一下module:状态管理器的模块组操作。
- 声明模块组:
在vuex/store.js中声明模块组,我们还是用我们的const常量的方法声明模块组。代码如下:
const moduleA={
state,mutations,getters,actions
}
声明好后,我们需要修改原来 Vuex.Stroe里的值:
export default new Vuex.Store({
modules:{a:moduleA}
})
- 在模板中使用
现在我们要在模板中使用count状态,要用插值的形式写入。
{{$store.state.a.count}}
如果想用简单的方法引入,还是要在我们的计算属性中rutrun我们的状态。写法如下:
computed:{
count(){
return this.$store.state.a.count;
}
},