本文已参与「新人创作礼」活动,一起开启创作之路

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

什么时候用到:如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果应用够简单,最好不要使用 Vuex。一个简单的 store 模式就足够所需了。本篇以学习Vuex(以及后续的pinia)为目的进行基础使用方式的记录

引入设置Vuex

安装

可以在package.json当中填写需要的包的名称和版本号,重启yarn服务

//package.json
{
  ...
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^3.0.0-0",
    "vuex": "^4.0.0-0"
  },
 ...
}

安装好vuex以后,在src/app 目录下创建 app.store.ts 的文件进行初始化

从vuex中引入createStore。创建一个store,它等于一个createStore对象。再默认导出store

// src/app/app.store.ts
import { createStore } from 'vuex';
// 创建 Store
const store = createStore({ });
export default store;

接下来去入口文件 main.ts 载入创建好的store。接下来就可以在应用中访问到store了。

import { createApp } from 'vue';
import App from './app/app.vue';
import appStore from './app/app.store';
const app = createApp(App);
//应用store
app.use(appStore);
app.mount('#app');

核心概念

数据

组件的数据(state)可以储存在store里面,可以跨组件进行访问。

State

state可以定义在 createStore中,做为一个对象可以定义需要用到的数据。

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  // 定义 state
  state: {
    name: 'Web.dev',
  },
});
export default store;

在应用中访问时,可以直接使用$store.state.name访问到刚刚定义的数据

// app.vue
<template>
  <h3>
    {{ $store.state.name }}
  </h3>
</template>

mapState

在组件中访问数据时,需要加上$store.state的前缀才能找到相应的数据。如果想要直接通过state的名称访问,可以在computed中使用mapState的帮手方法展开需要的state名称,之后在组件中就可以直接像组件内数据一样读取了。

// app.vue
<template>
  <h3>
    {{name }}
  </h3>
</template>
<script>
import { mapState } from 'vuex';
export default {
  computed: {
    ...mapState(['name']),
  },
};
</script>

如果想要为store里面的state另外起其他名称,可以在mapState提供一个对象。对象的key值就是新的名称。

// app.vue
<script>
...
  computed: {
    ...mapState({
      appName:'name',
    }),
  },
...
</script>

获取器:Getters

可以认为是 store 的计算属性。

在组件中,会用到对state的计算属性,如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它。所以可以使用Getters来统一处理。

app.store.js 中添加getter属性,函数名为在组件中使用的名称,给他一个state的参数,再返回处理后的结果

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  // 定义 state
  state: {
    name: 'Web.dev',
  },
  //定义 getter
  getters: {
    name(state) {
      return ` ${state.name}`;
    },
  },
});
export default store;

在组件中使用时,可以在computed中使用从vuex引入的mapGetters的帮手方法展开需要的getters名称,之后在组件中就可以直接像组件内数据一样读取了。

// app.vue
<script>
...
  computed: {
    ...mapGetters(['name']),
  },
...
</script>

修改器

store里面的数据无法在组件中直接修改,需要使用指定的修改器为数据更新值

Mutations

每一个mutations都是一个方法,每一个方法接受一个state参数和data参数。state就是需要修改的数据,data是传递进来的数值。

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  // 定义 state
  state: {
    name: 'Web.dev',
  },
  mutations: {
    setName(state, data) {
      state.name = data;
    },
  },
});
export default store;

在组件中使用时,需要使用 $store.commit() 方法。方法有两个参数,第一个参数是需要使用的mutations 的名称,第二个参数是需要传递的数据值。

// app.vue
<template>
  <h3 @click="onClickName">
    //name from store
    {{ name }}
  </h3>
</template>
<script>
export default {
  ...
  methods: {
    onClickName() {
      this.$store.commit('setName', 'Web开发者');
    },
  },
  ...
};
</script>

mapMutations

如果需要把moutations映射成组件的方法使用,可以用mapMutations帮手方法解构出来。

引入mapMutations帮手方法后,在methods中使用名称解构出来。结构出来后,直接传递数据就可以了

// app.vue
<template>
  ...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
  ...
  methods: {
    ...mapMutations(['setName'])
    onClickName() {
      //this.$store.commit('setName', 'Web开发者');
      this.setName('Web开发者');
    },
  },
  ...
};
</script>

动作

可以在action里面定义一些异步动作,比如从服务器获取数据或者发送数据到服务器。

如果需要从数据端获取一些数据,可以先在state中定义一个值,再定义一个修改这个statemutations,再定义一个获取数据的action。在action里面请求接口获取数据,再用mutation修改state中的数据。

Actions

定义一个action,为state 提供数据

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  //数据从action获取
  state: {
    name: '',
  },
  mutations: {
    setName(state, data) {
      state.name = data;
    },
  },
  //定义action
  actions: {
    getName(context) {
      const name = 'web.dev'
      context.commit('setName', name);
    },
  },
});
export default store;

action可以在组件的created生命周期中使用。在$store中找到dispatch方法,参数设置为action的名称。action会通过刚刚设置的mutationsstate中的name更新数据。

// app.vue
<template>
  ...
</template>
<script>
export default {
  ...
  created() {
    this.$store.dispatch('getName');
  },
  ...
};
</script>

mapActions

如需要直接在组件中使用actions的名称。可以使用mapActions在组件的methods中结构出来。支持数组和对象。

// app.vue
<template>
  ...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
  ...
  created() {
    this.getName();
  },
  methods: {
    ...mapActions(['getName'])
    ...mapMutations(['setName'])
    onClickName() {
      //this.$store.commit('setName', 'Web开发者');
      this.setName('Web开发者');
    },
  },
  ...
};
</script>

Context参数

Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.statecontext.getters 来获取 stategetters

可以通过参数结构来简化代码。

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  ...
  //定义action
  actions: {
    getName({commit}) {
      const name = 'web.dev'
      commit('setName', name);
    },
  },
});
export default store;

综合运用:加载状态

模拟一下异步加载的状态。需要设定一个state来表示是否加载成功(isLoading)。再去action中模拟从数据库获取数据的状态。

修改state的数据需要用到新的mutation setLoading

// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
  ...
  state: {
    name: '',
    loading: false,
  },
  mutations: {
    ...
    setLoading(state, data) {
      state.loading = data;
    },
  },
  //定义action
   actions: {
    getName({ commit }) {
      commit('setLoading', true);
      setTimeout(() => {
        const name = '宁皓网';
        commit('setName', name);
        commit('setLoading', false);
      }, 2000);
    },
  },
});
export default store;

再组件的created周期当中使用getNameaction,并且通过isLoading的状态来确认页面上的显示内容。这样组件再2秒后会显示name state 的内容

// app.vue
<template>
  <h3>
    {{ name }}
    <span v-if="isLoading">加载中...</span>
  </h3>
  ...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
  ...
  computed: {
    ...mapGetters(['name']),
    ...mapState(['loading']),
  },
  created() {
    this.getName();
  },
  methods: {
    ...mapActions(['getName'])
  },
  ...
};
</script>

进阶概念

modules

所有的数据都放在一个store,会让应用的架构变得臃肿。可以根据应用的需求,创建各自模块的store,单独储存各自的数据。例如把跟用户有关的内容放在 ./src/user目录里面,创建user.store.tsstore

每一个store都可以拥有自己的state、mutation、action等属性。

const store = {
  state: {
    currentUser: '',
  },
};
export default store;

在应用的app.store.ts 中导入刚刚创建的user store

import user, { UserState } from '@/user/user.store';
const store = createStore({
  ...
  // 使用 modules导入
  modules: {
    user,
  },
});
export default store;

使用TS时,为modules中的store添加mutation、action等属性需要设置类型。store的类型为Module带两个参数 store: Module<UserState, RootState>

UserState 参数为本地定义的interface,RootState是需要在app.store中定义

// user.store.ts
import { Module } from 'vuex';
import { RootState } from '@/app/app.store';
export interface UserState {
  currentUser: string;
}
const store: Module<UserState, RootState> = {
  state: {
    currentUser: '',
  },
  mutations: {
    setCurrentUser(state, data) {
      state.currentUser = data;
    },
  },
  ...
};
export default store;
//app.store.ts
import { createStore } from 'vuex';
import user, { UserState } from '@/user/user.store';
export interface RootState {
  name: string;
  loading: boolean;
  user?: UserState;
}
const store = createStore({
  ...
});
export default store;

namespaced

默认情况下modules的中store的 mutations、actions等属性是全局的名称。如果需要命名空间,可以在modules中打开。

// user.store.ts
import { Module } from 'vuex';
import { RootState } from '@/app/app.store';
...
const store: Module<UserState, RootState> = {
  namespaced: true,
  ...
};
export default store;

在开启命名空间以后,在组件中展开时,需要加上模块的名称。

// app.vue
<script>
import { mapMutations } from 'vuex';
export default {
  ...
  computed: {
    ...mapGetters({
      ...
      // 展开时加上模块的名字
      currentUser: 'user/currentUser',
    }),
  },
  ...
};
</script>

插件