Vuex学习心得

最近公司项目中使用Vuex做状态管理,就全面温习了一遍文档,然后在项目使用中遇到一些常见问题就一一总结下。

一、由来

我们知道Vue中数据是自顶向下单向流动的,但是以下两种情况单向数据流实现起来十分繁琐且不易维护:

  1. 多个视图依赖同一状态;
  2. 来自不同视图需要变更统一状态。

因此,Vuex诞生了。我们需要把不同组件的共享状态抽离出来,放在全局单例中管理,这样我们的组件树构成一个巨大的“视图网”,任何组件都可以获取共享状态并且以相同的规则变更状态。

Vuex都有一个“store”,包含应用中大部分状态。Vuex和全局对象有以下两点不同

  1. Vuex中的状态是响应式的,只要store中的状态发生改变,其他组件也会得到高效更新;
  2. 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时注意以下事项:

  1. 最好提前在你的 store 中初始化好所有所需属性;
  2. mutation必须是同步函数;
  3. 当需要在对象上添加新属性时,你应该:
  • 使用 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,不同之处在于:

  1. action提交的是mutation,而不是直接变更状态,无法直接操作state;
  2. 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)
}

 

posted on 2019-04-06 22:42  世界之魂  阅读(258)  评论(0编辑  收藏  举报

导航