现如今vue2.0的组件化开发思想已经深入人心,但是组件越是细化,组件之间的交流就显得苍白无力。若组件之间有某种“血缘”关系还好说(父子组件),毫无层级关系的组件,沟通交流起来确实存在“代沟”。还好有vuex状态管理,让所有状态都在托管在一个地方,状态管理起来也就很简单清晰了,状态管理可以分为四个模块:state、getter、mutation、action。下面我简单分享下它的使用方法。
1、先用vue-cli搭建一个webpack项目
2、在项目中安装vuex:
npm install vuex --save
3、在src文件夹下创建一个store文件夹里面创建一个store.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
// 先写一个简单的vuex
export default new Vuex.Store({
state: {
num: 0
},
mutations: {
add(state) {
state.num++
}
}
})
4、在main.js中import你的store.js
import store from './store/store'
new Vue({
el: '#app',
router,
store, // 可以让任何组件通过this.$store获取vuex中的东西
components: { App },
template: '<App/>'
})
5、简单的vuex已经创建好,现在可以在任何组件获取到vuex中的状态:
在HelloWorld组件中用一下:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>vuex中的num状态为:{{h2}}</h2>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
msg: 'vuex示例',
h2: this.$store.state.num // 直接用this.$store就可以取到整个状态树的信息
}
}
}
</script>
上图为以上步骤的效果
注意:上面那样(在data属性里获取store的state)会有个问题,在this.$store.commit(‘add’)的时候(即去提交状态的时候),视图并没有响应(这里是让num++),num一直为0。这是为什么呢?这就要说下vue中data属性和computed属性的区别:
1)、data 中的内容只会在 created 钩子触发前初始化一次,则data中的数据,仅仅是对state的一个引用。data 中内容依赖变更时(state更变),data 属性并不会变更(data并不会重新计算)
2)、computed 则是通过【依赖追踪】实现的,在 computed 求值时引用的 Vue 变量变化时,会触发对 computed 的重新计算。
一句话,就是因为computed会重新计算。
所以,请将获取的state的步骤放到computed属性中,为了变更state后可以反应到视图去:
computed: {
h2() {
return this.$store.state.num
}
}
这样,点击按钮,视图的h2就会+1,以下是我HelloWorld的代码:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<h2>vuex中的num状态为:{{h2}}</h2>
<button @click="change">点我改变num</button>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
name: 'HelloWorld',
data () {
return {
msg: 'vuex示例',
}
},
computed: {
h2() {
return this.$store.state.num
}
},
methods: {
change() {
this.$store.commit('add');
}
}
}
</script>
mapState 辅助函数
将store.js改下(多多一些state):
state: {
num: 0,
total: 0, // 多加了一些state
a: 1,
b: 2
}
如果HelloWorld组件中要得到所有state,只能在computed一个一个返回么?mapState 辅助函数给你一种简单的写法:
首先确保组件里:
import { mapState } from 'vuex'
则computed可以写成如下:
// mapState函数传入一个对象
computed: mapState({
// 箭头函数可使代码更简练
num: state => state.num,
// 传字符串参数 'num' 等同于 `state => state.num`
numAlias: 'num',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.num+ this.msg
}
})
如果computed的名字与vuex中的state名字一样,则更加简洁了:
// 注意mapState函数参数变成数组了
computed: mapState([
// 映射 this.num为 store.state.num
'num',
'total',
'a',
'b'
])
效果:
以上的computed都是返回直接返回mapState的调用结果,如果,这个组件computed不止有vuex中的状态的话,就要使用ES6对象展开运算符… 将本组件的compute数据和vuex中的状态进行合并:
computed: {
localComputed () { /* 本组件的computed数据 */ },
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
6、如果有时候,你需要对你的state进行第二次运算再使用,比如需要把状态num扩大两倍,再减去十(num*2 -10),而且不仅仅是一个地方使用(别的组件也需要这样的值),就可以用vuex的第二大模块:Getter
store.js里:
state: {
num: 0,
total: 0,
a: 1,
b: 2
},
// 使用getters对状态进行二次封装
getters: {
numDone: state=> state.num*2 -10
},
组件文件里:
computed: {
numDone(){
return this.$store.getters.numDone
},
...mapState([
'num',
'total',
'a',
'b'
])
},
效果:
Getter函数除了state还可以接收第二个参数:getter(次vuex中所有的getters集合),拿另一个getter做计算再返回:
getters: {
numDone2: (state, getters) => {
return Math.abs(getters.numDone)
}
}
store.getters.numDone2 // -> 10
你也可以通过让 getter 返回一个函数,来实现给 getter 传参。
// store.js
state: {
num: 0,
total: 0,
a: 1,
b: 2,
list: [
{
id: 1,
name: 'aaa'
},
{
id: 2,
name: 'bbb'
},
{
id: 3,
name: 'ccc'
},
]
},
getters: {
getListItemById: (state) => (id) => {
return state.list.find(item=> item.id === id)
}
},
// 组件中获得getter
store.getters.getListItemById(2) // -> { id: 2, name: 'bbb' }
同样地,Getter 也有mapGetters() 辅助函数,用法同mapState()
7、改变state像变量一样去赋值?可以是可以,但是代码太冗余太长不易阅读,所以vuex有一个Mutation的模块。它是用来专门更改 Vuex 的 store 中的状态的。
export default new Vuex.Store({
state: {
num: 0
},
mutations: { // 这里定义mutation里的事件回调函数
add(state) { // 第一个参数为所有state集合
state.num++
}
}
})
回调函数?官方文档是这样解释的:
Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个 回调函数 (handler)。而上面的add相当于事件注册,函数里面的代码等待add事件触发了再执行。
在任何组件中可以去通过comit函数去触发add事件:
this.$store.commit('add') // add为上诉说的字符串事件类型
提交到mutation中,从而改变num状态。
mutation的载荷:
mutations: {
add(state, n) { // 载荷n作为第二个参数
state.num += n
}
}
调用:
this.$store.commit('add', 10)
如果载荷有多个,可以写成对象传进来(一般也是这样写):
this.$store.commit('add', {
amount: 10,
a: 1,
b: 2
})
甚至为了好看,可以吧整个commit的参数写成对象形式:
this.$store.commit({
type: 'add',
amount: 10,
a: 1,
b: 2
})
mutations: {
add(state, payload) { // payload为一个载荷集合
console.log(payload) -> {amount: 10, a: 1, b: 2}
}
}
通过mutation改变state要注意:
1)、最好提前在你的 store 中初始化好所有所需属性
2)、state为对象,需要添加新属性时,你应该:
Vue.set(obj, ‘newProp’, 123) 或者
state.obj = { …state.obj, newProp: 123 }
这样state才会是响应式的
使用mutation时还要注意一点:
mutation 必须是同步函数,例:add中不能够是一个异步函数
同样地,mutation也有mapMutations函数:
import { mapMutations } from 'vuex'
export default {
// ...
mounted() {
this.add({
a: 1,
b: 2
})
},
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为`this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'add' // 将 `this.add(amount)` 映射为 `this.$store.commit('add', amount)`
]),
...mapMutations({
fn: 'increment' // 将 `this.fn()` 映射为 `this.$store.commit('increment')`
})
}
}
8、想要异步改变state?那你该用vuex的Action
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) { // 与 store 实例具有相同方法和属性的 context 对象
context.commit('increment')
}
}
也可以使用ES6的对象解构来传参:
actions: {
increment ({ commit }) {
commit('increment')
}
}
异步操作:
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
action 通过提交mutation来改变state,那为啥不直接用mutation好了?因为:
1)可以在 action 内部执行异步操作,异步操作完成后改变state。而mutation只能够同步操作去更改state
2)我的理解:action可以多个mutation组合使用
Action 在组件中通过 store.dispatch 方法触发(同提交mutation类似):
this.$store.dispatch('increment')
// 以载荷形式分发
this.$store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
this.$store.dispatch({
type: 'incrementAsync',
amount: 10
})
这是官方文档的一个购物车的例子:
在action中集合了许多mutation,由异步操作的不同结果来决定什么时候调用哪个mutation去改变状态(分发多重 mutation)
actions: {
checkout ({ commit, state }, products) {
// 把当前购物车的物品备份起来
const savedCartItems = [...state.cart.added]
// 发出结账请求,然后乐观地清空购物车
commit(types.CHECKOUT_REQUEST)
// 购物 API 接受一个成功回调和一个失败回调
shop.buyProducts(
products,
// 成功操作
() => commit(types.CHECKOUT_SUCCESS),
// 失败操作
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}
以上代码是直接在异步操作完成后,在action中去完成一些逻辑处理,也可以把它写成promise,resolve出来,在组件中dispatch后去写逻辑处理:
actions: {
actionA ({ commit }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
commit('someMutation')
resolve()
}, 1000)
})
}
}
在组件中:
this.$store.dispatch('actionA').then(() => {
// 以上的resolve()
})
在action也可以返回另外一个dispatch的promise
actions: {
// ...
actionB ({ dispatch, commit }) {
return dispatch('actionA').then(() => {
commit('someOtherMutation')
})
}
}
同样地,action也有mapActions方法,用法和mutation完全一致,会把组件的 methods 映射为 store.dispatch 调用