action

action 类似于 mutation,不同之处在于:

  • action 提交的是 mutation,而不是直接变更状态。

  • action 可以包含任意异步操作。

可以理解成,为了解决异步更改 state 的问题,需要在 mutation 前添加一层 action,我们直接操作 action,然后让 action 去操作 mutation,如示例代码 6-6-1 所示。

示例代码 6-6-1 提交 action
const store = Vuex.createStore({
    state: {
        count: 3,
    },
    mutations: {
        increment(state,params) {
            state.count = state.count + params.num
        }
    },
    actions: {
        incrementAction(context, params) {
            // 在 action 里面会去调用 mutations
            context.commit('increment', params)
        }
    }
})
const counter = {
    template: '<div>{{ count }}<button @click="clb">增加</button></div>',
    computed: {
        count() {
            return this.$store.state.count // 通过 this.$store.state 可以获取 state
        }
    },
    methods: {
        clb() {
            // 通过 this.$store.dispatch 调用 action
            this.$store.dispatch('incrementAction', {
                num: 4
            })
        }
    }
}

通过 this.$store.dispatch 可以在 Vue 组件中提交一个 action,同时可以传递自定义的参数,这和提交一个 mutation 很类似,乍一看感觉多此一举,直接提交 mutation 岂不是更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制吗?action 则不受这个约束。因此可以在 action 内部执行异步操作:

...
incrementAction (context, params) {
    setTimeout(()=>{
        context.commit('increment',params)
    },1000)
}
...

虽然不能在 mutation 执行时进行异步操作,但是可以把异步逻辑放在 action 中,这样对于 mutation 其实是同步的,Chrome 浏览器的 Vue DevTools 也就可以追踪到每一次的状态改变了。

同时,可以在 action 中返回一个 Promise 对象,以便准确地获取异步 action 执行完成后的时间点:

incrementAction (context, params) {
    return new Promise((resolve, reject) => {
        setTimeout(()=>{
            context.commit('increment',params)
        },1000)
    })
}
...
this.$store.dispatch('incrementAction').then(() => {...})

当然,也可以在一个 action 内部获取当前的 state 或者触发另一个 action,也可以触发一个 mutation,代码如下:

actions: {
     incrementAction(context) {
        if (context.state.count > 1) {
            context.dispatch('actionOther')

            context.commit('increment1')
            context.commit('increment2')
        }
    },
    actionOther() {
         console.log('actionOther')
    }
}

同样,与 gettersmutations 一样,在组件中使用 actions 时,可以用 mapActions 辅助函数来快速在 methods 中映射,代码如下:

...
methods:{
...Vuex.mapActions({
        clb:'incrementAction'
    })
}

Vuex 中,mapActions 是一个帮助函数,用于将 Vuex store 中的 actions 映射到组件的方法中。这使得你可以在组件中更方便地调用 storeactions,从而执行异步操作或复杂的业务逻辑。

为什么使用 mapActions

Actions 在 Vuex 中用于处理异步操作和复杂的业务逻辑。它们可以包含任意异步操作,并且可以通过 commit 提交 mutations 以改变 store 的状态。在组件中使用 mapActions 可以简化调用 actions 的过程,使代码更简洁、更可读。

基本用法

mapActions 可以将 Vuex store 中的 actions 映射为组件的方法。它有两种主要用法:

  1. 使用字符串数组:直接映射 store 中的 actions

  2. 使用对象:重命名方法或创建带参数的方法。

使用字符串数组

假设你的 Vuex store 中有如下 actions

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

在组件中,你可以使用 mapActions 将这些 actions 映射为组件的方法:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapActions(['increment', 'incrementAsync'])
  }
};
</script>

在这个示例中,incrementincrementAsync actions 被映射为组件的方法,可以直接在模板中使用。

使用对象

当你需要重命名方法或传递参数时,可以使用对象形式:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="incrementCount">Increment</button>
    <button @click="incrementCountAsync">Increment Async</button>
    <button @click="incrementByAmount(5)">Increment by 5</button>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapActions({
      incrementCount: 'increment', // 将 `increment` action 映射为 `incrementCount`
      incrementCountAsync: 'incrementAsync', // 将 `incrementAsync` action 映射为 `incrementCountAsync`
      incrementByAmount({ commit }, amount) {
        commit('incrementByAmount', amount);
      }
    })
  }
};
</script>

为了支持 incrementByAmount 方法,需要在 Vuex store 中定义相应的 actionmutation

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    },
    incrementByAmount(state, amount) {
      state.count += amount;
    }
  },
  actions: {
    increment({ commit }) {
      commit('increment');
    },
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
    incrementByAmount({ commit }, amount) {
      commit('incrementByAmount', amount);
    }
  }
});

结合局部方法

如果组件中既需要使用 Vuex store 的 actions,又需要使用组件的局部方法,可以将它们结合起来:

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
    <button @click="localMethod">Local Method</button>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';

export default {
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapActions(['increment', 'incrementAsync']),
    localMethod() {
      console.log('This is a local method');
    }
  }
};
</script>

在这个示例中,组件既有来自 Vuex store 的 actions 方法,也有组件自己的 localMethod 方法。

模块化 Vuex

在使用 Vuex 模块化时,可以指定模块名称来访问嵌套的 actions

const store = new Vuex.Store({
  modules: {
    myModule: {
      state: () => ({
        count: 0
      }),
      mutations: {
        increment(state) {
          state.count++;
        }
      },
      actions: {
        increment({ commit }) {
          commit('increment');
        },
        incrementAsync({ commit }) {
          setTimeout(() => {
            commit('increment');
          }, 1000);
        }
      }
    }
  }
});

在组件中:

<template>
  <div>
    <p>Module Count: {{ count }}</p>
    <button @click="increment">Increment</button>
    <button @click="incrementAsync">Increment Async</button>
  </div>
</template>

<script>
import { mapActions, mapState } from 'vuex';

export default {
  computed: {
    ...mapState('myModule', ['count'])
  },
  methods: {
    ...mapActions('myModule', ['increment', 'incrementAsync'])
  }
};
</script>

在这个示例中,myModule 模块中的 incrementincrementAsync actions 被映射为组件的方法。

mapActionsVuex 提供的一个非常方便的工具,可以简化在组件中调用 store actions 的过程。通过字符串数组和对象的两种形式,你可以灵活地映射 storeactions 到组件的方法中,并在需要时结合局部方法和模块化的 store 使用。这样,你的代码将更加清晰、简洁,并且易于维护。