1x1 Vuex 核心 - Getter & Action

417 阅读2分钟

Vuex 基础教程系列 🎉

Getters

Vuex 的 getters 选项可以看做是 Store 的计算属性 (computed),它可以基于一个或多个状态派生出全新的状态。getter 也具有缓存的特性,只有当它的依赖值发生改变时才会重新计算。

当多个组件共享同一个派生状态时,使用 getter 将是最有效的,无需让每个组件单独定义计算属性来过滤或组合出派生的状态,也无需通过导入模块的方式使用共享函数进行过滤或组合。

首先,让我们通过 getter 来组合一个新的状态。

export default {
    state: {
        firstName: 'Eich',
        lastName: 'Brendan'
    },
    getters: {
        fullName: (state) => state.lastName + state.firstName
    }
}

接着,使用 getter 对状态进行过滤,并且通过第二个参数我们还可以访问 getters 选项

const store = new Vuex.Store({
    state: {
        firstName: 'Brendan ',
        lastName: 'Eich',
        names: ['James Gosling', 'Tim Berners-Lee'],
    },
    getters: {
        fullName: state => state.firstName + state.lastName,
        namesStartWithT: (state, **getters**) => {
            const combines = state.names.concat([getters.fullName]);
            return combines.filter(item => item.toLowerCase().startsWith('t'));
        }
    }
});

getter 还可以返回一个函数,这对 Store 中的状态进行更灵活的过滤非常有用。

const store = new Vuex.Store({
    state: {
        firstName: 'Brendan ',
        lastName: 'Eich',
        names: ['James Gosling', 'Tim Berners-Lee'],
    },
    getters: {
        fullName: state => state.firstName + state.lastName,
        findNames: (state, getters) => **(name)** **=>** **{**
            const combines = state.names.concat([getters.fullName]);
            return combines.filter(item => item.indexOf(name) !== -1);
        **}**
    }
});

//Use
store.getters.findNames('a');

注意的是,若 getter 返回的是一个函数,那么计算的结果并不会被缓存。

在 Vue 组件访问 Getter

  1. 导入 store 实例或者通过根组件注入的 $store 来访问 getters 选项。
  2. 使用 mapGetters 工具方法。

mapGetters 工具方法

使用 mapGetters 方法可以很方便的将 getter 映射为组件的计算属性。

使用数组的取值方式,可以快速的映射与 getter 同名的计算属性

{
	computed: Vuex.mapGetters(['fullName', 'names'])
}

如需自定义计算属性的名称,可以使用对象取值格式。

{
	computed: Vuex.mapGetters({ name: 'fullName' })
}

最后千万别忘记了可以使用 ES6 展开运算符将映射的计算属性与其它计算属性进行合并。

Actions

actions 是对 mutations 的再次包装,原因在于后者不仅被项目代码所依赖还会被 Vuex 自身的状态变更记录所依赖,因此只能纯粹的进行同步状态变更操作。而 actions 则是专门用来服务业务代码,它同时支持同步与异步的状态变更操作。

action 不能直接变更状态,必须要在其中提交 mutation 的方式来间接变更状态。

下图是一个异步状态变更的具体流程示意图:

Untitled.png

上图中的 Backend API 表示的是在 action 中发起接口请求获取后端数据,而 Devtools 则是 vue-devtools 在调试 Vuex 应用时需要同步的从 mutation 中获取状态数据以及变更记录。

下面是实现上图的基本示例:

<div id="app">
    <p>{{age}} / {{name}}</p>
    <button @click="updateInfo">UpdateInfo</button>
</div>
const store = new Vuex.Store({
    state: {
        age: 1,
        name: 'Sam'
    },
    mutations: {
        updateAge(state) {
            state.age = 18;
        },
        updateName(state) {
            state.name = 'Bob';
        }
    },
    actions: {
        asyncUpdateAge(context) {
            context.commit('updateAge');
        },
        asyncUpdateName({ commit }) {
            commit('updateName');
        }
    }
});

new Vue({
    el: '#app',
    store,
    computed: Vuex.mapState(['age', 'name']),
    methods: {
        updateInfo() {
            this.$store.dispatch('asyncUpdateAge');
            this.$store.dispatch('asyncUpdateName');
        }
    }
})

actions 的使用形式与 mutations 完全相同, 细微的差异体现于:

  1. action 使用 dispatch 方法进行分发, muation 则是使用 commit 方法进行提交。
  2. action 的事件处理方法 (handler) 接收一个与 store 具有相同属性和方法的 context 对象,(但两者并非真的是同一个对象),因此你可以通过 context.commit 来提交 mutation ;通过 context.getters 来获取 getter ;通过 context.state 来获取 state;通过 context.dispatch 来分发其它 action。借助 ES6 的解构运算符,还可以简化参数的获取 ({ commit, dispatch, state, getters })

分发与载荷 (Payload)

action 通过 dispatch 方法进行触发,它与 muationcommit 方法具有相同形式的载荷

const store = new Vuex.Store({
      state: {
          age: 0,
          name: 'Sam'
      },
      mutations: {
          updateAge(state, payload) {
              state.age = payload;
          },
          updateName(state, payload) {
              state.name = payload.name;
          }
      },
      actions: {
          asyncUpdateAge(context, payload) {
              context.commit('updateAge', payload);
          },
          asyncUpdateName({ commit }, payload) {
              commit('updateName', payload);
          }
      }
});

dispatch 方法分发 action 并传递载荷。

this.$store.dispatch('asyncUpdateAge', 20);
this.$store.dispatch('asyncUpdateName', { name: 'Bob' });
//or
this.$store.dispatch({
	type:'asyncUpdateName',
	name:'Bob'
})
💡 我们更推荐以对象的形式来提交载荷,因为它能传递更多的参数并且可阅读性更强。

Actions + Promise

action 中返回一个 Promise 对象这可以让我们监听异步状态变更的完成时机。

const store = new Vuex.Store({
    state: {
        age: 0,
        name: 'Sam'
    },
    mutations: {
        updateAge(state, payload) {
            state.age = payload.age;
        },
        updateName(state, payload) {
            state.name = payload.name;
        }
    },
    actions: {
        asyncUpdateAge(context, payload) {
            return new Promise(resolve => {
                setTimeout(() => {
                    context.commit('updateAge', payload);
                    resolve();
                }, 1000);
            })
        },
        asyncUpdateName({ commit, dispatch }, payload) {
            return new Promise(resolve => {
                setTimeout(() => {
                    commit('updateName', payload);
                    resolve()
                }, 1500);
            });
        },
        asyncUpdateInfo({ dispatch }, payload) {
            const updateAge = dispatch('asyncUpdateAge', payload);
            const updateName = dispatch('asyncUpdateName', payload);
            return Promise.all([updateAge, updateName])
        }
    }
});

通过 dispatch 方法中返回的 Promise 状态来执行对应的回调。

this.$store.dispatch({
    type: 'asyncUpdateInfo',
    name: 'Bob',
    age: 18
}).then(() => {
    alert('success!!!')
})

最后,为了避免陷入回调地狱我们还可以使用最新的 asyncawait 语法来替代 .then 回调。

async updateInfo() {
      await this.$store.dispatch({...})
}

在 Vue 组件中分发 Action

  1. 通过导入 store.dispatch 的方式或使用根组件注入的 $store 来分发 action
  2. 使用 mapActions 工具方法将 action 映射为组件实例的方法。

mapActions 工具方法

使用 mapActions 工具方法我们可以很方便的将 Store 的 action 映射为组件的方法。以实现更方便的调用。

使用数组格式可以很快的将 actions 映射为组件实例同名的方法。

{
	methods: Vuex.mapActions(['asyncUpdateInfo'])
}

如果需要重新命名方法则可以使用对象格式

{
	methods: Vuex.mapActions({
		updateInfo : 'asyncUpdateInfo'
	})
}