Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

基本状态管理模式

以下是一个简单的计数应用:

  1. new Vue({
  2. // state
  3. data () {
  4. return {
  5. count: 0
  6. }
  7. },
  8. // view
  9. template: `
  10. <div>{{ count }}</div>
  11. `,
  12. // actions
  13. methods: {
  14. increment () {
  15. this.count++
  16. }
  17. }
  18. })

其中:

  • state: 驱动应用的数据源
  • view: 以声明方式将 state 映射到视图
  • actions: 响应在 view 上的用户输入导致的状态变化

基本的状态管理模式是一个单向数据流,但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:

  • 多个视图依赖于同一状态
  • 来自不同视图的行为需要变更同一状态

这时候,可以把组件的共享状态抽取出来,在一个全局的单例模式下进行管理

Vuex 存储结构

  1. const state = {
  2. }
  3. // getters
  4. const getters = {
  5. }
  6. // actions
  7. const actions = {
  8. }
  9. // mutations
  10. const mutations = {
  11. }
  12. export default {
  13. namespaced: false,
  14. state,
  15. getters,
  16. actions,
  17. mutations
  18. }

接着统一在 index.js 中进行管理

  1. import Vue from 'vue'
  2. import Vuex from 'vuex'
  3. import ChildMod from './modules/ChildMod'
  4. Vue.use(Vuex)
  5. const debug = process.env.NODE_ENV !== 'production'
  6. export default new Vuex.Store({
  7. modules: {
  8. ChildMod,
  9. },
  10. strict: debug,
  11. })

State

即单一状态树, 用一个对象就包含了全部的应用层级状态

store

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 1
  4. },
  5. })

基本用法

  1. const Counter = {
  2. template: `<div>{{ count }}</div>`,
  3. computed: {
  4. count () {
  5. return this.$store.state.count
  6. }
  7. }
  8. }

mapState 辅助函数用法

在 computed 中使用

  1. // 在单独构建的版本中辅助函数为 Vuex.mapState
  2. import { mapState } from 'vuex'
  3. export default {
  4. // ...
  5. computed: mapState({
  6. // 箭头函数可使代码更简练
  7. count: state => state.count,
  8. // 传字符串参数 'count' 等同于 `state => state.count`
  9. countAlias: 'count',
  10. // 为了能够使用 `this` 获取局部状态,必须使用常规函数
  11. countPlusLocalState (state) {
  12. return state.count + this.localCount
  13. }
  14. })
  15. }

使用对象展开运算符写法

  1. computed: {
  2. localComputed () { /* ... */ },
  3. // 使用对象展开运算符将此对象混入到外部对象中
  4. ...mapState({
  5. // ...
  6. })
  7. }

Getters

有时候我们需要从 store 中的 state 中派生出一些状态

store

  1. const store = new Vuex.Store({
  2. state: {
  3. todos: [
  4. { id: 1, text: '...', done: true },
  5. { id: 2, text: '...', done: false }
  6. ]
  7. },
  8. getters: {
  9. doneTodos: state => {
  10. return state.todos.filter(todo => todo.done)
  11. },
  12. doneTodosCount: (state, getters) => {
  13. return getters.doneTodos.length
  14. },
  15. getTodoById: (state) => (id) => {
  16. return state.todos.find(todo => todo.id === id)
  17. }
  18. }
  19. })

基本用法

  1. computed: {
  2. doneTodosCount () {
  3. return this.$store.getters.doneTodosCount
  4. }
  5. }

通过方法访问,getter 返回一个函数

  1. // -> { id: 2, text: '...', done: false }
  2. this.$store.getters.getTodoById(2)

mapGetters 辅助函数用法

在 computed 中使用

  1. import { mapGetters } from 'vuex'
  2. export default {
  3. // ...
  4. computed: {
  5. // 使用对象展开运算符将 getter 混入 computed 对象中
  6. ...mapGetters([
  7. 'doneTodosCount',
  8. 'anotherGetter',
  9. // ...
  10. ])
  11. }
  12. }

如果你想将一个 getter 属性另取一个名字,使用对象形式:

  1. mapGetters({
  2. // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
  3. doneCount: 'doneTodosCount'
  4. })

Mutations

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

PS: Mutation 必须是同步函数

store

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 1
  4. },
  5. mutations: {
  6. increment (state, payload) {
  7. // 变更状态
  8. state.count += payload.amount
  9. }
  10. }
  11. })

基本用法

  1. this.$store.commit('increment', {
  2. amount: 10
  3. })

对象风格提交方式

  1. this.$store.commit({
  2. type: 'increment',
  3. amount: 10
  4. })

mapMutations 辅助函数用法

在 methods 中使用

  1. import { mapMutations } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapMutations([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
  7. // `mapMutations` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
  9. ]),
  10. ...mapMutations({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
  12. })
  13. }
  14. }

Actions

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态
  • Action 可以包含任意异步操作

store

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,但不是 store 实例本身

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 0
  4. },
  5. mutations: {
  6. increment (state, payload) {
  7. state.count += payload.amount
  8. }
  9. },
  10. actions: {
  11. increment (context) {
  12. context.commit('increment')
  13. },
  14. incrementAsync ({ commit }, payload) {
  15. setTimeout(() => {
  16. commit('increment', payload)
  17. }, 1000)
  18. }
  19. }
  20. })

基本用法

  1. // 基础分发方式
  2. this.$store.dispatch('increment')
  3. // 以载荷形式分发
  4. this.$store.dispatch('incrementAsync', {
  5. amount: 10
  6. })
  7. // 以对象形式分发
  8. this.$store.dispatch({
  9. type: 'incrementAsync',
  10. amount: 10
  11. })

mapActions 辅助函数用法

在 methods 中使用

  1. import { mapActions } from 'vuex'
  2. export default {
  3. // ...
  4. methods: {
  5. ...mapActions([
  6. 'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
  7. // `mapActions` 也支持载荷:
  8. 'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
  9. ]),
  10. ...mapActions({
  11. add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
  12. })
  13. }
  14. }

回调方式用法

store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch 仍旧返回 Promise。因此可以这么用:

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

Module

Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块