Vuex整洁架构之道

如何保持Vuex架构的整洁和可维护,本文将探讨如何为Vuex创建整洁架构的技巧,这个架构的灵感来自于Vuex官方文档和互联网的其他vuex的学习资料。

Vuex整洁架构之道

之前也写过一遍关于VUE项目代码优化的文章《Vue 开发中可以使用的 ES6 新特征

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

为了简化起见,Vue是一个采用基于组件的体系结构的框架,因此每个组件都被设计成一小块Vue文件,并可在组件之间重用。正因为如此,它并不排除某些小组件需要一起工作或使用相同数据的可能性。这就是为什么需要Vuex来解决这个问题。Vuex就像一个全局变量和全局函数,拥有的每个组件都可以使用它。Vuex更集中地管理API调用和存储响应数据,统一API调用的入口,这样一个API调用和数据就可以在Vue项目的每个组件中使用。

Github:https://github.com/QuintionTang/star-wars.git

如何注册和创建Vuex模块

本文将使用vuex构建一个简单的vue应用程序,来显示《星球大战》电影中的人物和行星列表。

首先,需要基于每个作用域创建一个新的store module。下面是创建store module的模板。每个store module都包含state, mutations, actionsgetters。确保添加了namescaped属性,避免store module之间名称冲突,当然这也意味着访问store module之前需要指定namescaped名称,这样可以使得 Vuex 更加整洁和可维护。

const state = {}
const mutations = {}
const actions = {}
const getters = {}
export default {
    namespaced: true,
    state,
    mutations,
    actions,
    getters,
};

创建完所需的store module后,将所有的store module 合并到一个vuex store中。

实际项目中,可以直接在下面的代码中创建一个store modulestategettersactions)。而无需创建诸如people模块或planets模块之类的单独模块。

为了达到架构清晰,建议不要那样做,最好创建几个小模块,并将其合并成一个vuex store中。

import Vue from "vue";
import Vuex from "vuex";
import people from "./modules/people";
import planets from "./modules/planets";

Vue.use(Vuex);

const store = new Vuex.Store({
    modules: {
        people: people, // 这种方式是可以重命名模块名称
        planets,
    },
});

export default store;

之后,将几个store module组合在一起,然后将其捆绑成一个module。下一步是创建Vue应用程序时注册它,以便Vue应用程序可以使用所有Vuex store moudle。

单个store module

在单独的store模块的其余部分中,仅关注people store module,因为在people与planets模块之间大部份是相似。

首先,从state开始,它就像一个包含数据的全局变量。在本文例子中,需要存储《星球大战》中的人物数据。因为有多个person数据,所以将其存储在一个数组中。在本例中,用空数组的默认值定义了一个people属性。其实state的设计有点像数据库的设计。

const state = {
  people: []
}

其次,在想要存储的全局变量之后,接下来需要创建一个mutations,以便能够修改状态值。因为它类似于一个全局变量,所以需要在不调用mutations的情况下保护要编辑的state,只有mutations才能更改state值。在本例中,在修改state值时使用了(...)展开操作符,在JavaScript中,如果只使用equals(=),则值只复制引用,而不是深入复制数组值。这很容易引起错误,以至于难以调试。因此,在改变新值时最好使用(...)展开操作符。对于对象,也要确保使用(...)展开操作符。

const mutations = {
    setPeople(state, payload) {
        state.people = [...payload];
    },
};

接下来就是actionsactions就像一个全局函数,可以在每个组件中调用。大多数情况下,actions用于处理API调用和更改state值。在actions中,通常是mutations来改变state的值。

在我看来,也是为了更整洁的构建。建议为API创建一个新的单独文件,本例中,将Axios的定义放在api文件夹中,接口方法放在services中(意思是与服务器对接的方法)。

import axios from "axios";

const apiClient = axios.create({
    baseURL: "https://swapi.dev/api/",
});
// interceptors 拦截器,统一处理接口的请求,如修改header等
apiClient.interceptors.request.use((request) => {
    return request;
});
// interceptors 拦截器,统一处理接口的响应和错误
apiClient.interceptors.response.use(
    (response) => {
        return response;
    },
    (error) => {
        console.error(error);
    }
);

export default apiClient;

那么在services中,就是处理单个的方法。

import apiClient from "@/api";

const getPeople = (success, fail) => {
    apiClient
        .get("people")
        .then((response) => success(response))
        .catch((response) => fail(response));
};

const getPlanets = (success, fail) => {
    apiClient
        .get("planets")
        .then((response) => success(response))
        .catch((response) => fail(response));
};

export { getPeople, getPlanets };

在完成并准备好所有API调用及相应的调用方法后,现在回到的store module中。将定义的API方法与store module结合起来。

import * as services from "@/services";

const state = {
    people: [],
};
const mutations = {
    setPeople(state, payload) {
        state.people = [...payload];
    },
};
const actions = {
    getPeople({ commit }, { success, fail } = {}) {
        services.getPeople(
            (response) => {
                commit("setPeople", response.data.results);
                success && success(response);
            },
            (response) => {
                fail && fail(response);
            }
        );
    },
};
const getters = {
    male(state) {
        return state.people.filter((person) => person.gender === "male") || [];
    },
    female(state) {
        return (
            state.people.filter((person) => person.gender === "female") || []
        );
    },
};

export default {
    namespaced: true,
    state,
    mutations,
    actions,
    getters,
};

视图或组件调用store

首先,让从state开始。要访问state,需要从Vuex导入mapState,并将其映射到一个computed值,如下面的示例代码所示。

import { mapActions, mapState, mapGetters } from "vuex";
export default {
    name: "People",
    created() {
        this.getPeople();
    },
    computed: {
        ...mapState("people", ["people"]),
        ...mapGetters("people", ["male", "female"]),
    },
};

接下来就是actions,需要从Vuex导入mapActions并将其放置在方法中。

import { mapActions, mapState, mapGetters } from "vuex";
export default {
    name: "People",
    data() {
        return {
            loading: false,
        };
    },
    created() {
        this.loading = true;
        this.getPeopleAction();
    },
    computed: {
        ...mapState("people", ["people"]),
        ...mapGetters("people", ["male", "female"]),
    },
    methods: {
        ...mapActions("people", ["getPeople"]), // 如果需要是多个namespace的方法,在下面在写一行
        // ...mapActions("planets", ["getPlanets"]),  // 此处为行星模块
        handleSuccess() {
            this.loading = false;
            console.log("success fetch data");
        },
        handleFail() {
            this.loading = false;
            console.log("failed fetch data");
        },
        getPeopleAction() {
            this.getPeople({
                success: this.handleSuccess,
                fail: this.handleFail,
            });
            // 下面注释的代码为优化前的调用方式
            // this.$store.dispatch("people/getPeople", {
            //     success: this.handleSuccess,
            //     fail: this.handleFail,
            // });
        },
    },
};

总结

Vuex是状态管理,它存储Vue应用程序的所有数据逻辑。必须确保它的整洁,以助于团队协作。