vuex 使用总结(详解)

###### 若是以前未使用过 vuex 请务必先看一下参考html

参考:vue

什么状况下应该使用 Vuex?

Vuex 能够帮助咱们管理共享状态,并附带了更多的概念和框架。这须要对短时间和长期效益进行权衡。git

若是不打算开发大型单页应用,应用够简单,最好不要使用 Vuex。一个简单的 store 模式就足够了。可是,若是须要构建一个中大型单页应用,就要考虑如何更好地在组件外部管理状态,Vuex 是不错的选择。es6

使用

在 Vue 的单页面应用中使用,须要使用Vue.use(Vuex)调用插件。将其注入到Vue根实例中。github

import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  getter: {
    doneTodos: (state, getters) => {
      return state.todos.filter(todo => todo.done)
    }
  },
  mutations: {
    increment (state, payload) {
      state.count++
    }
  },
  actions: {
    addCount(context) {
      // 能够包含异步操做
      // context 是一个与 store 实例具备相同方法和属性的 context 对象
    }
  }
})
// 注入到根实例
new Vue({
  el: '#app',
  // 把 store 对象提供给 “store” 选项,这能够把 store 的实例注入全部的子组件
  store,
  template: '<App/>',
  components: { App }
})

而后改变状态:vuex

this.$store.commit('increment')

核心

State,Getter,Mutation,Action,Module,

Vuex 主要有四部分:数组

  1. state:包含了store中存储的各个状态。
  2. getter: 相似于 Vue 中的计算属性,根据其余 getter 或 state 计算返回值。
  3. mutation: 一组方法,是改变store中状态的执行者,只能是同步操做
  4. action: 一组方法,其中能够包含异步操做

State

Vuex 使用 state 来存储应用中须要共享的状态。为了能让 Vue 组件在 state更改后也随着更改,须要基于state 建立计算属性。缓存

// 建立一个 Counter 组件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count  // count 为某个状态
    }
  }
}

Getter

相似于 Vue 中的 计算属性(能够认为是 store 的计算属性),getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被从新计算。app

Getter 方法接受 state 做为其第一个参数:框架

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

经过属性访问

Getter 会暴露为 store.getters 对象,能够以属性的形式访问这些值:

store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }]

Getter 方法也接受 state和其余getters做为前两个参数。

getters: {
  // ...
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
store.getters.doneTodosCount // -> 1

咱们能够很容易地在任何组件中使用它:

computed: {
  doneTodosCount () {
    return this.$store.getters.doneTodosCount
  }
}

注意: getter 在经过属性访问时是做为 Vue 的响应式系统的一部分缓存其中的。

经过方法访问

也能够经过让 getter 返回一个函数,来实现给 getter 传参。在对 store 里的数组进行查询时很是有用。

getters: {
  // ...
  getTodoById: (state) => (id) => {
    return state.todos.find(todo => todo.id === id)
  }
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }

注意: getter 在经过方法访问时,每次都会去进行调用,而不会缓存结果。

Mutation

更改 Vuex 的 store 中的状态的惟一方法是提交 mutation。也就是说,前面两个都是状态值自己, mutations才是改变状态的执行者。

注意:mutations只能是同步地更改状态。

Vuex 中的 mutation 很是相似于事件:每一个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是咱们实际进行状态更改的地方,而且它会接受 state 做为第一个参数:

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变动状态
      state.count++
    }
  }
})

调用 store.commit 方法:

store.commit('increment')

提交载荷(Payload)

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}
this.$store.commit('increment', 10)

其中,第一个参数是state,后面的参数是向 store.commit 传入的额外的参数,即 mutation 的 载荷(payload)

store.commit方法的第一个参数是要发起的mutation类型名称,后面的参数均当作额外数据传入mutation定义的方法中。

规范的发起mutation的方式以下:

// 以载荷形式
store.commit('increment',{
  amount: 10   //这是额外的参数
})

// 或者使用对象风格的提交方式
store.commit({
  type: 'increment',
  amount: 10   //这是额外的参数
})

额外的参数会封装进一个对象,做为第二个参数传入mutation定义的方法中。

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

Action

想要异步地更改状态,就须要使用actionaction并不直接改变state,而是发起mutation

注册一个简单的 action:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Action 函数接受一个与 store 实例具备相同方法和属性的 context 对象,所以你能够调用 context.commit 提交一个 mutation,或者经过 context.statecontext.getters 来获取 state 和 getters。当咱们在以后介绍到 Modules 时,你就知道 context 对象为何不是 store 实例自己了。

实践中,咱们会常常用到 ES2015 的 参数解构 来简化代码(特别是咱们须要调用 commit 不少次的时候):

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

在action内部执行异步操做:

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

发起action的方法形式和发起mutation同样,只是换了个名字dispatch

// 以对象形式分发Action
store.dispatch({
  type: 'incrementAsync',
  amount: 10
})
Actions 支持一样的载荷方式和对象方式进行分发

Action处理异步的正确使用方式

想要使用action处理异步工做很简单,只须要将异步操做放到action中执行(如上面代码中的setTimeout)。

要想在异步操做完成后继续进行相应的流程操做,有两种方式:

  1. store.dispatch返回相应action的执行结果,而action的处理函数返回的就是Promise,因此store.dispatch仍然返回一个Promise。

    actions: {
      actionA ({ commit }) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            commit('someMutation')
            resolve()
          }, 1000)
        })
      }
    }

    如今能够写成:

    store.dispatch('actionA').then(() => {
      // ...
    })

    在另一个 action 中也能够:

    actions: {
      // ...
      actionB ({ dispatch, commit }) {
        return dispatch('actionA').then(() => {
          commit('someOtherMutation')
        })
      }
    }
  2. 利用async/await 进行组合action。代码更加简洁。

    // 假设 getData() 和 getOtherData() 返回的是 Promise
    
    actions: {
      async actionA ({ commit }) {
        commit('gotData', await getData())
      },
      async actionB ({ dispatch, commit }) {
        await dispatch('actionA') // 等待 actionA 完成
        commit('gotOtherData', await getOtherData())
      }
    }
    一个 store.dispatch 在不一样模块中能够触发多个 action 函数。在这种状况下,只有当全部触发函数完成后,返回的 Promise 才会执行。

Action与Mutation的区别

Action 相似于 mutation,不一样在于:

  • Action 提交的是 mutation,而不是直接变动状态。
  • Action 能够包含任意异步操做,而Mutation只能且必须是同步操做。

Module

因为使用单一状态树,应用的全部状态会集中到一个比较大的对象。当应用变得很是复杂时,store 对象就有可能变得至关臃肿。

这时咱们能够将 store 分割为模块(module),每一个模块拥有本身的 stategettersmutationsactions 、甚至是嵌套子模块——从上至下进行一样方式的分割。

代码示例:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

嵌套子模块

首先建立子模块的文件:

// products.js

// initial state
const state = {
  added: [],
  checkoutStatus: null
}
// getters
const getters = {
  checkoutStatus: state => state.checkoutStatus
}
// actions
const actions = {
  checkout ({ commit, state }, products) {
  }
}
// mutations
const mutations = {
  mutation1 (state, { id }) {
  }
}
export default {
  state,
  getters,
  actions,
  mutations
}

而后在总模块中引入:

import Vuex from 'vuex'
import products from './modules/products' //引入子模块
Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    products   // 添加进模块中
  }
})

各个模块与 Vue 组件结合

stategetter结合进组件须要使用计算属性

computed: {
    count () {
      return this.$store.state.count 
      // 或者 return this.$store.getter.count
    }
  }

mutationaction结合进组件须要在methods中调用this.$store.commit()或者this.$store.commit():

methods: {
    changeDate () {
        this.$store.commit('change');
    },
    changeDateAsync () {
        this.$store.commit('changeAsync');
    }
}

为了简便起见,Vuex 提供了四个辅助函数方法用来方便的将这些功能结合进组件。

  1. mapState
  2. mapGetters
  3. mapMutations
  4. mapActions

示例代码:

import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'

export default {
    // ...
    computed: {
      localComputed () { /* ... */ },
        // 使用对象展开运算符将此对象混入外部对象中
      ...mapState({
        // 为了可以使用 `this` 获取局部状态,必须使用常规函数
        count(state) {
          return state.count + this.localCount
        }
      }),
      ...mapGetters({
        getterCount(state, getters) {
          return state.count + this.localCount
        }
      })
    }
    methods: {
      ...mapMutations({
          // 若是想将一个属性另取一个名字,使用如下形式。注意这是写在对象中
           add: 'increment' // 将 `this.add()` 映射为`this.$store.commit('increment')`
        }),
      ...mapActions({
          add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
        })
    }
}

若是结合进组件以后不想改变名字,能够直接使用数组的方式。

methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`

      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
    ]),
}
为什么使用展开运算符: mapState 等四个函数返回的都是一个对象。咱们如何将它与局部计算属性混合使用呢?一般,咱们须要使用一个工具函数将多个对象合并为一个,以使咱们能够将最终对象传给 computed 属性。可是有了 对象展开运算符,咱们就能够进行简化写法。

Vuex的使用差很少就是这样。还有命名空间的概念,大型应用会使用。能够点这里查看

附:项目结构

Vuex 不限制代码结构。可是规定了一些须要遵照的规则:

  1. 应用层级的状态应该集中到单个 store 对象中。
  2. 提交 mutation 是更改状态的惟一方法,而且这个过程是同步的。
  3. 异步逻辑都应该封装到 action 里面。

只要你遵照以上规则,如何组织代码随你便。若是 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。

对于大型应用,咱们会但愿把 Vuex 相关代码分割到模块中。下面是项目结构示例:
项目结构示例

相关文章
相关标签/搜索