引入 Vuex Store 管理 Vue 组件数据状态的更新和获取


为了更优雅地管理页面组件的数据状态,你可以通过引入 Vuex 来存储和更新博客文章的数据信息。

封装 API 接口请求

学院君之前已经在构建单页面应用项目骨架中安装过 Vuex,所以这里直接使用就好了。

不过,在此之前,我们还要在 resources/js 目录下新建一个 api.js 来统一管理所有 API 接口请求处理:

const base = '/api/posts';

export default {
    // 请求文章详情页 API
    getPostData (id) {
        return axios.get(base + '/' + id);
    },

    // 请求博客首页 API
    getPostsData (page = 1) {
        return axios.get(base, {
            params: {page: page}
        });
    },

    // 请求博客分类页 API
    getCategoryPosts (name, page = 1) {
        return axios.get(base + '/category/' + name, {
            params: {page: page}
        });
    },

    // 请求分页数据 API
    getMorePosts (url) {
        return axios.get(url);
    }
}

这里,我们仅在每个方法中返回请求处理的 Promise 对象,具体的响应实体和错误处理放到 Vuex Store 中实现。

定义 Vuex Store

接下来,在同级目录下新建 stores.js 来定义 Vuex Store,目前我们的所有数据状态都是围绕博客文章,所以只需要定义一个跟级别的 Store 即可:

import Vue from 'vue';
import Vuex from 'vuex';
import PostAPI from './api';

Vue.use(Vuex);

export default new Vuex.Store({
    state: {
        postData: {},
        postLoaded: false,
        postsData: [],
        postsLink: {},
        postsLoaded: false
    },
    getters: {
        getPostData (state) {
            return state.postData;
        },
        getPostLoaded(state) {
            return state.postLoaded;
        },
        getPostsData(state) {
            return state.postsData;
        },
        getPostsLink(state) {
            return state.postsLink;
        },
        getPostsLoaded(state) {
            return state.postsLoaded;
        }
    },
    mutations: {
        setPostData(state, post) {
            state.postData = post;
        },
        setPostLoaded(state, loaded) {
            state.postLoaded = loaded;
        },
        setPostsData(state, posts) {
            state.postsData = posts;
        },
        setPostsLink(state, links) {
            state.postsLink = links;
        },
        setPostsLoaded(state, loaded) {
            state.postsLoaded = loaded;
        }
    },
    actions: {
        loadPost(context, id) {
            PostAPI.getPostData(id).then((resp) => {
                context.commit('setPostData', resp.data.data);
                context.commit('setPostLoaded', true);
            }).catch((err) => {
                console.log(err);
            });
        },
        loadPosts(context, name = null) {
            let request;
            if (name) {
                request = PostAPI.getCategoryPosts(name);
            } else {
                request = PostAPI.getPostsData();
            }
            request.then((resp) => {
                context.commit('setPostsData', resp.data.data);
                context.commit('setPostsLink', resp.data.links);
                context.commit('setPostsLoaded', true);
            }).catch((err) => {
                console.log(err);
            });
        },
        loadMorePosts(context, url) {
            PostAPI.getMorePosts(url).then((resp) => {
                let postsData = context.getters.getPostsData;
                postsData.push(...resp.data.data);
                context.commit('setPostsData', postsData);
                context.commit('setPostsLink', resp.data.links);
                context.commit('setPostsLoaded', true);
            }).catch((err) => {
                console.log(err);
            });
        }
    }
});

其中的 state 用于定义该实体的状态字段,getters 用于定义各个状态的「计算属性」,mutations 用于定义各个状态的更新逻辑(与 getters 对应的「setters」),actions 用于初始化不同状态值(真正编写业务逻辑对状态值进行初始化/更新的地方,支持异步操作,状态值的更新通过 commit 提交触发)。

详细的语法细节参考 Vuex 官方文档核心概念部分。

我们在 actions 中通过引入上一步编写的 API 接口请求方法,在请求成功后对状态值进行更新。

其实可以看到,我们这里是将上篇教程中散落在不同 Vue 组件中的 API 请求处理、模型数据属性初始化、更新和获取逻辑拆分到 api.jsstores.js 中完成了,这样一来,可以方便对 API 请求、数据状态做统一管理,提高代码复用性,将数据状态和 Vue 组件进行解耦也更加有利于大型项目的模块化开发。

在 Vue 实例中引入

编写好 Vuex Store 后,还需要将其引入到 Vue 实例中,才能在 Vue 组件中使用,我们在 app.js 中编写引入代码如下:

...

import routes from './routes';
import store from './stores';

...

const app = new Vue({
    el: '#app',
    store,
    router: new VueRouter(routes)
});

在 Vue 组件中通过 Vuex 存取数据状态

至此,我们就可以正式在 Vue 页面组件中使用 Vuex 来更新和获取博客文章的数据状态了。

Home.vue

以首页 Home.vue 为例,重构页面组件代码如下:

...

<script>
import PostList from './common/List';
import Loading from "./common/Loading";
export default {
    components: {PostList, Loading},
    created() {
        this.$store.dispatch('loadPosts');
    },
    computed: {
        posts() {
            return this.$store.getters.getPostsData;
        },
        links() {
            return this.$store.getters.getPostsLink;
        },
        loaded() {
            return this.$store.getters.getPostsLoaded;
        }
    },
    methods: {
        getMorePosts(nextPageUrl) {
            this.$store.dispatch('loadMorePosts', nextPageUrl);
        }
    }
}
</script>

模板代码不需要动,只需要更改脚本代码即可:

  1. 通过 created 钩子函数定义数据状态初始化逻辑,注意我们需要通过 Store 实例的 dispatch 方法调用 actions 中的方法;
  2. 定义计算属性,通过 Store 实例中定义的 getters 替换之前的组件模型数据属性值(postslinksloaded)获取;
  3. 之前定义的模型属性初始化方法都被迁移到 Vuex Store 了,这里仅保留了 getMorePosts 方法获取分页数据,具体实现也转化成了调用 Vuex Store 中定义的 actions 方法完成。

Category.vue

按照同样的思路重构分类列表 Category.vue 组件的实现代码:

...

<script>
import PostList from './common/List';
import Loading from "./common/Loading";

export default {
    components: {PostList, Loading},
    data () {
        return {
            name: ''
        }
    },
    created () {
        this.updateCategoryName();
    },
    watch: {
        '$route': 'updateCategoryName'
    },
    computed: {
        posts() {
            return this.$store.getters.getPostsData;
        },
        links() {
            return this.$store.getters.getPostsLink;
        },
        loaded() {
            return this.$store.getters.getPostsLoaded;
        }
    },
    methods: {
        updateCategoryName ()  {
            this.name = this.$route.params.name;
            this.$store.dispatch('loadPosts', this.name);
        },
        getMorePosts(nextPageUrl) {
            this.$store.dispatch('loadMorePosts', nextPageUrl);
        }
    }
}
</script>

Post.vue

和文章详情页 Post.vue 组件的实现代码:

...

<script>
import PostContent from './common/Content';
import Loading from "./common/Loading";

export default {
    components: {PostContent, Loading},
    created() {
        this.$store.dispatch('loadPost', this.$route.params.id);
    },
    computed: {
        post() {
            return this.$store.getters.getPostData;
        },
        loaded() {
            return this.$store.getters.getPostLoaded;
        }
    }
}
</script>

具体实现的细节就不一一介绍了。

你可以重新编译前端资源,刷新博客应用首页,然后体验分类页和详情页,可以看到页面的渲染效果和上篇教程的效果完成一样。

通过 Vue Devtools 跟踪 Vuex 数据状态

如果页面数据渲染不正常,你还可以在浏览器开发者工具中通过 Vue Devtools 的 Vuex 标签页跟踪各个数据状态的变化,方便调试和定位问题:

-w1420

本项目的完整代码已提交到 Github 代码仓库:https://github.com/nonfu/demo-spa,为了以示区别,非 Vuex 版被打上了 without-vuex 标签。


Vote Vote Cancel Collect Collect Cancel

<< 上一篇: 通过后端接口返回数据渲染 Vue 页面组件并实现分页功能

>> 下一篇: 基于 Laravel + Vue + Vuex 实现博客应用文章发布功能