Vuex学习心得
最近公司项目中使用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) }