最近公司项目中使用Vuex做状态管理,就全面温习了一遍文档,然后在项目使用中遇到一些常见问题就一一总结下。
一、由来
我们知道Vue中数据是自顶向下单向流动的,但是以下两种情况单向数据流实现起来十分繁琐且不易维护:
- 多个视图依赖同一状态;
- 来自不同视图需要变更统一状态。
因此,Vuex诞生了。我们需要把不同组件的共享状态抽离出来,放在全局单例中管理,这样我们的组件树构成一个巨大的“视图网”,任何组件都可以获取共享状态并且以相同的规则变更状态。
Vuex都有一个“store”,包含应用中大部分状态。Vuex和全局对象有以下两点不同
- Vuex中的状态是响应式的,只要store中的状态发生改变,其他组件也会得到高效更新;
- store中的状态不能直接改变,只能显式的提交mutation来更新。
二、概念
State
Vuex使用单一状态树,state作为应用的唯一数据源而存在。当我们需要从store获取状态时,只需在组件计算属性中直接返回即可。使用mapState辅助函数可以更方便我们生成计算属性。
state.js
const state = {
count: 0
} export default state
component.js
import { mapState } from 'vuex'
export default {
name: 'Vuex',
data() {
return {
}
},
computed: {
localComputed: () => {},
...mapState({
count: state => state.count
})
},
methods: {
}
}
Vuex并不意味着所有状态都必须放在store中,若有些状态属于单个组件,最好作为组件的局部状态存在为好。
Getters
getter通俗来讲就是state的计算属性,方便我们从state中派生出一些状态出来。getter接受state、getter作为其第一个、第二个参数。
const getters = {
derCount: state => {
return state.count+1
},
doneLists: (state, getters) => {
return state.todoLists.filter(list => list.status)
},
todoCount: (state, getters) => {
return state.todoLists.length - getters.doneLists.length
},
} export default getters
获取getter对象可通过属性访问this.$store.getters.doneLists,同样我们可以通过mapGetters辅助函数获取:
...mapGetters([
'doneLists',
'todoCount'
])
Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation,只有mutation中才能直接操作state。Vuex 中的 mutation 非常类似于事件,不能直接调用mutation handler。你需要先在mutattion中注册handler,然后在action或组件中调用此函数。每个mutation接受state,payload作为其第一个、第二个参数。
const mutations = {
addCount: (state) => {
state++
},
updateList: (state, index) => {
let updateList = state.todoLists[index]
let status = updateList.status
status = status?0:1
state.todoLists[index].status = status
}
} export default mutations
Vuex的store是响应式的,因此使用mutation时注意以下事项:
- 最好提前在你的 store 中初始化好所有所需属性;
- mutation必须是同步函数;
- 当需要在对象上添加新属性时,你应该:
- 使用
Vue.set(obj, 'newProp', 123)
, 或者 - 以新对象替换老对象。例如,利用 stage-3 的对象展开运算符我们可以这样写:
state.obj = { ...state.obj, newProp: 123 }
你可以在组件中使用 this.$store.commit('xxx')
提交 mutation,或者使用 mapMutations
辅助函数将组件中的 methods 映射为 store.commit
methods: {
localMethods() { },
...mapMutations([
'updateList',
'addCount'
])
}
Action
action类似于mutation,不同之处在于:
- action提交的是mutation,而不是直接变更状态,无法直接操作state;
- action支持异步操作。
action函数接受和store相同属性、方法的context对象,同样支持提交载荷方式。action通过store.dispatch的方式来分发。
const actions = {
addCountAsync: ({commit}) => {
commit('addCount')
},
deleteListAsync: ({commit}, index) => {
setTimeout(() => {
commit('deleteList',index)
},1000)
}
} export default actions
你在组件中使用 this.$store.dispatch('xxx')
分发 action,或者使用 mapActions
辅助函数将组件的 methods 映射为 store.dispatch
调用(需要先在根节点注入 store
)
...mapActions([
'addCountAsync',
'deleteListAsync' //将this.deleteListAsync()映射为this.$store.dispatch('deleteListAsync')
]),
Module
Vuex是单一状态树,应用所有状态都集中在一个比较大的对象中。当项目足够大时,store对象会变得很臃肿。Vuex允许我们分割store为子模块,每个modules都拥有自己的state、getters、mutations、actions、mudules。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true
的方式使其成为带命名空间的模块。
- getter接受参数依次为局部state、getters、根节点状态rootState、根节点rootGetter;
- mutation局部状态state、payload作为第一、第二个参数;
- action局部状态为context.state,根节点状态为context.rootState。{state,commit,rootState,rootGetters};
const state = {
bookLists: [
{name: '三国演义', status: 1},
{name: '西游记', status: 1},
{name: '红楼梦', status: 0},
{name: '水浒传', status: 0}
]
} // getters
const getters = {
/**
* @param {[type]} state、getters [局部状态]
* @param {[type]} rootState、rootGetters [根节点状态]
*/
readBook: (state, getters, rootState, rootGetters) => {
return state.bookLists.filter(list => list.status)
}
} // mutations
const mutations = {
// state 局部状态
readOver (state, { index }) {
state.bookLists[index].status = 1
},
reRead (state, { index }) {
state.bookLists[index].status = 0
},
delete (state, { index }) {
state.bookLists.splice(index, 1)
}
} // actions
const actions = {
/**
* dispatch、commit局部化
* 提交是接受root访问根dispatch、commit
*/
deleteAsync ({ dispatch, commit, state, getters, rootState, rootGetters }, index) {
commit('delete',{index:index})
commit('reduceLast',null,{root:true})
}
} export default {
namespaced: true,
state,
getters,
mutations,
actions
}
若在子模块内部想在全局命名空间提交commit、分发action,将{root:true}作为第三参数传给commit、dispatch即可。
当在组件中使用带命名空间的子模块时,可以将空间名称作为第一参数传给相应map函数:
computed:
...mapState('book', {
bookLists: state => state.bookLists,
}),
methods:
...mapMutations('book', [
'readOver',变更状态
'reRead'
]),
...mapActions('book', [
'deleteAsync',
]),
三、使用技巧
在严格模式下使用Vuex时,使用v-model将state与表单绑定,修改表单值会出现报错。严格模式规定无论何时修改state状态且不是由于mutation引起就会抛出错误。这时我们可以在组件中将所需状态深拷贝一份,进行模板渲染或操作变更,最后再深拷贝一份提交mutation变更状态。这样组件内的操作就不受state影响。
获取深拷贝状态:
stateData() {
let _stateData = JSON.parse(JSON.stringify(this.$store.state.stateData))
this.data = _stateData
return this.$store.state.stateData
}
提交mutation:
setData(data) {
let setData = JSON.parse(JSON.stringify(data))
this.$store.commit('setStateData',setData)
}