🏝 宁皓网十周年庆优惠活动, 查看介绍立即订阅 →

Vue.js 3 应用开发学习指南(四):应用状态管理(Vuex)

Vue 应用里的组件可以管理自己的状态,在组件内部可以声明它需要用的数据,通过组件内部的一些方法可以修改组件需要的数据,改变组件的状态,组件的状态发生变化以后它就会重新被渲染,显示更新之后的状态。在复杂的应用里,组件之间可能需要共享一些状态,比如当用户在评论组件里发表了一条新的评论,内容组件上的评论数量可能需要更新一下。这种情况就需要引入一套应用整体的状态管理解决方案。

对于一个简单的 Vue 应用来说,引入状态管理解决方案会让应用变得复杂一些,但是相信我,您只要打算正经用 Vue 开发个应用,就一定需要一套状态管理的解决方案,目前 Vue 官方提供的方案就是使用  Vuex 这个东西。这篇文章我们就一块儿了解一下基于 Vuex 的状态管理。

Vuex

Vuex 是 Vue 官方提供的一套应用状态管理解决方案。先把它安装在我们的 Vue 项目里。

安装  Vuex

package.json<修改>

  "dependencies": {
    // ...
    "vuex":  "^4.0.0-0"
  },

在项目的 package.json 文件的 dependencies 里面添加一个 vuex,设置一下版本号。然后在终端,项目所在目录的下面,执行 yarn 或者 npm install,安装好项目的依赖。

Store

有了 Vuex 以后,可以用它提供的功能去创建一个 Store,这个 Store 可以想成是 Vue 应用的数据仓库,它里面有组件需要的数据(state),更新数据(mutations)用的方法,还有处理数据用的一些动作(actions)。

比如应用的内容列表组件需要一组内容列表数据,就可以在 Store 里面,添加一个叫 posts 的数据,然后定义一个获取这组数据用的动作,一般就是请求服务端接口获取数据,得到了数据可以提交修改,设置 Store 里的 posts 这个数据。在内容列表组件里面,可以引用调用 Store 里的动作获取 posts 这个数据,然后在组件里面使用这个数据。

1:创建 Store

src/app/app.store.js<新建>

import { createStore } from 'vuex';

/**
 * 创建 Store
 */
const store = createStore({
  state: {
    appName: '宁皓网',
  },

  modules: {},
});

/**
 * 默认导出
 */
export default store;

创建一个叫 app.store.js 的文件,放在 src/app 的下面。在这个文件里用 vuex 提供的 createStore 创建一个 Store,起个名字叫 store,默认导出这个 store。使用 createStore 的时候可以提供一个对象参数,在这个对象里可以定义 Store 里的数据,修改,动作这些东西。等会我们再学习这些东西。

注意我在创建这个 Store 的时候,用了一个 modules ,它里面可以包含一些 Store 模块。等会我们会去创建 Store 模块,再把它放在这个 modules 里面。这样做可以把应用的 Store 分割成不同的模块,每个 Store 模块里都可以有自己的数据,修改还有动作。这么做是为了应付复杂的应用,可以让应用的结构更清晰。

2:应用 Store

main.js<修改>

// ...
import appStore from './app/app.store';

// ...

/**
 * 应用 Store
 */
app.use(appStore);

// ...

有了 Store 以后,要告诉应用一声使用这个 Store。在应用的入口文件里面,创建了 app 的下面,使用 app.use 方法,把上面导入的 appStore 交给这个方法。这样在应用所有的组件里就都可以使用这个 store 了,可以通过 this.$store 看到应用的 Store 里的东西。你可以找一个组件,添加一个 created 生命周期方法,把 this.$store 输出到控制台上检查一下,看看它里面都有什么东西。

Store 模块

我打算按应用里处理的资源,把 Store 分割成不同的模块,这样可以应对复杂的应用,让应用的结构更清晰。每个 Store  模块里都可以管理自己的数据,有自己的修改,动作,也可以继续包含其它的 Store 模块。

我要把内容资源相关的东西放在一个 Store 模块里,然后根据对内容的操作继续划分出一些 Store 模块。这样我就需要创建一个 post 模块,然后再给内容列表,单个内容,创建、更新内容再单独创建一个 Store 模块,把这些内容模块放在 post 这个模块里,也就是在 post 这个 Store 模块里会包含内容列表,创建,更新内容这些 Store 模块。再把这个 post 模块放在应用的 Store 里,作为 Store 的一个模块。

1:内容列表 Store 模块

src/post/index/post-index.store.js<新建>

export const postIndexStoreModule = {
  namespaced: true,

  state: {
    loading: false,
    posts: [],
  },
};

内容列表相关的东西我会放在 src/post/index 这个目录的下面,所在可以在这个目录下面新建一个 Store 模块,文件的名字是 post-index.store.js。

在这个文件里导出一个叫 postIndexStoreModule 的东西,它的值是一个对象,这个对象就是一个 Store 模块,里面可以添加数据、修改、动作之类的东西。暂时我们只添加一个 state,它的值是这个 Store 模块里的数据。

注意这里我把 namespaced 设置成了 true,意思是要使用命名空间。这样在访问这个 Store 的修改、动作这些东西的时候就需要加上模块的名字。等会儿我们会看到例子。

2:内容 Store 模块

src/post/post.store.js<新建>

import { postIndexStoreModule } from './index/post-index.store';

export const postStoreModule = {
  namespaced: true,

  modules: {
    index: postIndexStoreModule,
  },
};

跟内容资源相关的东西是在 src/post 目录的下面,在这个目录的下面创建一个 post.store.js。在文件里可以定义并导出一个 Store 模块,在这个 Store 模块里可以在 modules 里面设置它包含的模块,我们把之前创建的 postIndexStoreModule 添加到这里,重新起个名字叫 index。

注意这个 post 模块也把 namespaced 设置成了 true,就是要使用命名空间。这样假设在 index 模块里有个 getPosts 动作,访问这个动作的时候用的是 'post/index/getPosts' 。

3:修改应用的 Store 

src/app/app.store.js<修改>

// ...
import { postStoreModule } from '@/post/post.store';

/**
 * 创建 Store
 */
const store = createStore({
  // ...

  modules: {
    post: postStoreModule,
  },
});

// ...

现在我们要把 post 这个 Store 模块添加到应用的 Store 模块里,把它放在 modules 里面,起个名字叫 post,对应的 Store 模块就是 postStoreModule。

获取器

在 Store 模块里可以定义一些获取器(getters),它们的作用主要就是提供 Store 模块里的数据(state)。当然我们可以不用通过获取器直接就能获取到 Store 模块里的数据。用获取器的好处是可以对数据进行加工处理,然后返回处理之后的数据。你可以在 Store 模块里定义任意数量的获取器提供不同的数据。

1:定义获取器

src/post/index/post-index.store.js<修改>

export const postIndexStoreModule = {
  namespaced: true,

  state: {
    loading: false,
    posts: [],
  },

  getters: {
    loading(state) {
      return state.loading;
    },

    posts(state) {
      return state.posts;
    },
  },
}

在 postIndexStoreModule 这个模块里添加一个 getters 属性,里面可以定义一些获取器,每个获取器都是一个方法,方法接收一个 state 参数,这个参数的值就是这个 Store 模块里的 state。也就是在这个模块里你可以从 state 参数里面得到 loading 与 posts 这两个数据。

获取器 return 的东西就是它提供的值。在组件里可以从 Store 里面访问获取器,可以直接绑定输出它们的值。上面我们定义了两个获取器,loading 还有 posts,loading 提供的就是这个 Store 模块里的 loading 这个数据的值,posts 获取器提供的就是 posts  这个数据的值。

注意这个获取器的名字是可以随便定义的,不一定非要跟 state 里的数据的名字一致。在获取器里面你可以处理一下要提供的数据,然后再返回处理之后的结果。

2:使用获取器

在应用的组件里如果需要使用 postIndexStoreModule 这个模块里的 loading 这个获取器提供的值,可以访问 this.$store.getters['post/index/loading'] ,如果你想直接访问 Store 里的这个模块的 loading 数据,可以访问 this.$store.state.post.index.loading 。

通过 vuex 提供的 mapGetters 方法,可以直接把 Store 里的  Getter 映射成一个组件的计算属性,这样就可以直接使用这个计算属性了。

src/post/index/component/post-list.vue<修改>

<script>
import { mapGetters } from 'vuex';
// ...

export default {
  computed: {
    ...mapGetters({
      loading: 'post/index/loading',
      posts: 'post/index/posts',
    }),
  },
  
  // ...
};
</script>

在内容列表组件里可以使用 postIndexStoreModule 里的 loading 还有 posts 这两个获取器提供的数据。先把之前在这个组件里添加的 data 方法删除掉,因为组件需要的数据我们会在 Store 里获取到。

从 vuex 里面导入 mapGetters,然后在组件里添加一个 computed 选项,把执行 mapGetters 返回的结果入进来,给这个方法可以提供一个对象参数,里面分别设置一下在这个组件里的计算属性的名字,还有对应的是 Store 里的哪个获取器。比如 posts 这个计算属性对应的获取器是 post/index/posts。这样我们就可以直接在这个组件里使用 posts 这个数据了。

修改

要改变 Store 里的数据(state),不能直接设置 state 的值,要通过提交(commit)修改(mutations)来完成。也就是你要在 Store 里面定义一些 Mutations,它们的作用就是去修改特定的 state 的值。

src/post/index/post-index.store.js<修改>

export const postIndexStoreModule = {

  // ...

  mutations: {
    setLoading(state, data) {
      state.loading = data;
    },

    setPosts(state, data) {
      state.posts = data;
    },
  },
};

postIndexStoreModule 里面先添加一个 mutations 选项,里面定义两个修改,每个修改都是一个方法,方法接收一个 state 参数,还有一个 data 参数。这个 state 参数里的东西就是这个 Store 模块里的 state 的值。 data 参数的值就是在提交这个修改的时候设置的值。

上面定义了两个 Mutation,一个是 setLoading,一个是 setPosts ,setLoading 会修改 loading 这个 state,setPosts 可以修改 posts 这个 state。提交修改可以在 Store 模块里的动作里执行,也可以在应用的组件里去做。

在组件里提交修改可以这样:

this.$store.commit('post/index/setLoading', true);

上面这个例子就是在组件里用 Store 提供的  commit 这个方法提交了一个叫 post/index/setLoading 的修改,第二个参数的值是 true,这个值会交给 post/index/setLoading 这个修改的第二个参数,也就是那个 data 参数。 提交了这个修改,它会把模块里的 loading 这个 state 的值修改成 true 。

在组件里我们也可以通过 vuex 提供的 mapMutations,把 Store 模块里的修改映射成组件里的一个方法,这样就可以直接执行这个方法来修改 Store 里的数据了。

示例:

methods: {
  ...mapMutations({
    setLoading: 'post/index/setLoading'
  })
}

在组件里可以把执行 mapMutations 返回的结果放到组件的 methods 选项里,这样会在组件里然后在组件里可以这样修改提交:

this.setLoading(true);

动作

在 Store 里面可以定义一些动作,比如请求服务端接口获取数据用的动作(actions),向服务端接口提交数据的动作等等。在动作里面获取到数据以后可以提交修改(mutations),用这种东西去修改 Store 里的数据(state)。数据被修改之后,使用这个数据的组件会被重新渲染显示更新之后的内容。

1:定义动作

src/post/post-index.store.js<修改>

import { apiHttpClient } from '@/app/app.service';

export const postIndexStoreModule = {

  // ...
  actions: {
    async getPosts({ commit }) {
      commit('setLoading', true);

      try {
        const response = await apiHttpClient.get('/posts');
        commit('setLoading', false);
        commit('setPosts', response.data);

        return response;
      } catch (error) {
        commit('setLoading', false);

        throw error;
      }
    },
  },
};

在 postIndexStoreModule 这个 Store 模块里定义一个叫 getPosts 的动作,把它放在 actions 这个属性里。Store 模块里的动作支持一个 context 参数,在这个参数里,你可以得到当前这个 Store 模块里的 state,getters ,也就可以得到 Store 整体的 rootState 还有 rootGetters 这些东西。

这里我们用了一个解构的写法,把 context 里的 commit 这个方法解构出来,这样就可以直接在这个动作里使用 commit 提交修改了。

在这个 getPosts 动作里,我先 commit 了一下 setLoading 这个 mutation,把模块的 loading 这个 state 的值设置成了 true。然后用了一组 try,catch 区块。catch 到了错误以后可以 commit 一下 setLoading,把 loading 的值改成 false。然后可以 throw 这个错误。

在 try 里面,我们用 apiHttpClient 这个 HTTP 客户端请求了服务端应用的内容列表接口,地址就是 /posts,请求成功得到了数据以后 commit 一个 setLoading ,把 loading 的值改成 false,再 commit 一个 setPosts,把 posts 这个数据的值设置成响应里的 data,也就是服务端响应里的具体的数据。

2:使用动作

src/post/index/components/post-list.vue<修改>

<script>
import { mapGetters, mapActions } from 'vuex';
// ...

export default {
  // ...
  methods: {
    ...mapActions({
      getPosts: 'post/index/getPosts',
    }),
  },

  // ...
};
</script>

在内容列表组件(PostList) 里,我们可以执行在 postIndexStoreModule 里定义的那个 getPosts 动作,去请求获取内容列表数据。这里借助 vuex 提供的 mapActions 帮手方法,把 Store 模块里的动作映射成为一个组件里的方法。

之前我们在这个组件的 methods 里面定义过一个 getPosts,这里要把这个方法删除掉,还要把在这个组件里导入 apiHttpClient 的代码也删除掉。

现在这个内容列表组件的 created 生命周期里执行的 getPosts 方法,其实执行的就是 postIndexStoreModule 里的那个 getPosts 动作,这个动作会去请求内容列表接口,然后提交 setPosts 修改,设置这个  Store 模块里的 posts 数据。之前我们在这个组件里已经使用了 posts 这个获取器,它提供的值就是一组内容列表数据。

3:预览

在浏览器上访问一下内容列表页面,你会看到一组内容列表数据,现在这个页面上的 PostList 组件里要展示的数据会来自 postIndexStoreModule 这个 Store 模块。

项目代码

https://github.com/ninghao/ninghao-vue-dev-3/tree/vuex

相关文章



微信好友

用微信扫描二维码,
加我好友。



微信公众号

用微信扫描二维码,
订阅宁皓网公众号。



240746680

用 QQ 扫描二维码,
加入宁皓网 QQ 群。

统计

13559
分钟
0
你学会了
0%
完成

社会化网络

关于

微信订阅号

扫描微信二维码关注宁皓网,每天进步一点