七、使用 Vuex 管理应用状态

在同级组件之间传输数据非常容易,但想象一下,让组件树对任何数据更改做出反应。您需要在事件总线中触发事件或通过所有父组件发送事件,直到它到达事件链的顶部,然后一直发送到所需组件;这个过程可能非常乏味和痛苦。如果您正在开发一个大规模的应用,那么这个过程是不可持续的。

Flux 库的开发是为了帮助实现这一过程,其思想是将反应性带到组件边界之外,因为 Vuex 能够维护数据的单一真实来源,同时也是您拥有业务规则的地方。

在本章中,我们将学习如何使用 Vuex,开发我们的存储,将其应用于我们的组件,并对其命名,以便在同一存储中拥有 Vuex 的不同模块。

在本章中,我们将介绍以下配方:

  • 创建简单的 Vuex 存储
  • 创建和理解 Vuex 状态
  • 创建和理解 Vuex 突变
  • 创建和理解 Vuex 操作
  • 创建和理解 Vuex getter
  • 使用 Vuex 创建动态组件
  • 为开发添加热模块重新加载
  • 创建 Vuex 模块

技术要求

在本章中,我们将使用Node.jsVue CLI

Attention, Windows users, you need to install an NPM package called windows-build-tools, to be able to install the following required packages. To do it, open PowerShell as administrator and execute the following command: > npm install -g windows-build-tools

要安装 Vue CLI,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> npm install -g @vue/cli @vue/cli-service-global

创建简单的 Vuex 存储

在应用中创建一个单一的真实来源可以简化数据流,使数据的反应性能够流入另一个视角,在这个视角下,您不再与父子关系相联系。数据现在可以存储在一个地方,每个人都可以获取或请求数据。

在本教程中,我们将学习如何安装 Vuex 库并创建第一个单一存储,以及如何使用反应式操作和数据获取程序对其进行操作。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要创建 Vue CLI 项目,请执行以下步骤:

  1. 我们需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:
> vue create initial-vuex
  1. CLI 将询问一些有助于创建项目的问题。您可以使用箭头键进行导航,输入键继续,空格键选择一个选项。

  2. 启动新项目有两种方法。默认的方法是基本的babeleslint项目,没有任何插件或配置,以及Manually模式,您可以在其中选择更多模式、插件、过梁和选项。我们将选择Manually

? Please pick a preset: (Use arrow keys)
 default (babel, eslint)
❯ Manually select features
  1. 现在,我们被问及项目中需要哪些功能。这些特性是一些 Vue 插件,如 Vuex 或路由(Vue-Router),测试员,linter 等等。选择BabelRouterVuexLinter / Formatter
? Check the features needed for your project: (Use arrow keys) ❯ Babel
  TypeScript
  Progressive Web App (PWA) Support ❯ Router ❯ Vuex
  CSS Pre-processors ❯ Linter / Formatter
  Unit Testing
  E2E Testing
  1. 通过选择 linter 和 formatter 继续此过程。在本例中,我们将选择ESLint + Airbnb配置:
? Pick a linter / formatter config: (Use arrow keys)
  ESLint with error prevention only ❯ ESLint + Airbnb config ESLint + Standard config 
  ESLint + Prettier
  1. 在设置了 linting 规则之后,我们需要定义它们何时应用于代码。可应用于on save或固定于on commit
? Pick additional lint features: (Use arrow keys)
  Lint on save ❯ Lint and fix on commit
  1. 在定义了所有这些插件、linter 和处理器之后,我们需要选择存储设置和配置的位置。存储它们的最佳位置是在专用文件中,但也可以将它们存储在package.json文件中:
? Where do you prefer placing config for Babel, ESLint, etc.? (Use 
  arrow keys) ❯ In dedicated config files  In package.json 
  1. 现在,您可以选择是否要将此选择设置为未来项目的预设,因此无需再次重新选择所有内容:
? Save this as a preset for future projects? (y/N) n

我们的配方将分为两部分:

  • 创建商店
  • 使用 Vuex 创建被动组件

让我们开始吧。

创建商店

现在您有了 Vuex 库的项目,我们需要创建第一个存储。在以下步骤中,我们将创建 Vuex 存储:

  1. src/store文件夹中打开index.js
  2. state属性中,添加一个名为counter的新键,并将该值设置为0
state: {
  counter: 0,
},
  1. mutations属性中,添加两个新函数incrementdecrement。这两个函数都有一个state参数,它是当前的 Vuexstate对象。increment功能将counter增加1,而decrement功能将counter减少1
mutations: {
  increment: (state) => {
    state.counter += 1;
  },
  decrement: (state) => {
    state.counter -= 1;
  },
},
  1. 最后,在actions属性中,添加两个新函数incrementdecrement。这两个函数都有一个解构参数commit,它是一个调用 Vuex 变异的函数。在每个函数中,我们将执行commit函数,将当前函数的名称作为字符串作为参数传递:
actions: {
 increment({ commit }) {
 commit('increment');
 },
 decrement({ commit }) {
 commit('decrement');
 },
},

使用 Vuex 创建被动组件

现在已经定义了 Vuex 存储,您需要与它进行交互。我们将创建一个无功组件,该组件将在屏幕上显示当前状态counter,并显示两个按钮,一个用于增加counter,另一个用于减少counter

单文件组件

这里我们将编写单文件组件的<script>部分:

  1. src文件夹中打开App.vue文件。
  2. 在文件中创建<script>节,其中包含一个export default对象:
<script>
  export default {};
</script>
  1. 在新创建的对象中,添加 Vuecomputed属性和名为counter的属性。在此属性中,我们需要返回当前的$store.state.counter
computed: {
  counter() {
    return this.$store.state.counter;
  },
},
  1. 最后,创建一个带有两个函数的 Vuemethods属性,incrementdecrement。两个函数都将执行一个参数为字符串形式的函数名的$store.dispatch
methods: {
  increment() {
    this.$store.dispatch('increment');
  },
  decrement() {
    this.$store.dispatch('decrement');
  },
},

单文件组件

让我们对单文件组件的<template>部分进行编码:

  1. 打开src文件夹中的App.vue文件。
  2. <template>部分,移除div#app中的所有内容。
  3. 创建一个[T0]HTML 元素,其中包含计数器变量。
  4. 在调用increment函数的@click指令上创建一个带有事件侦听器的按钮,并将+作为标签:
<button @click="increment">+</button>
  1. 在调用decrement函数的@click指令上创建一个带有事件侦听器的按钮,并将-作为标签:
<button @click="decrement">-</button>

要运行服务器并查看组件,您需要打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> npm run serve

以下是渲染并运行的组件:

它是如何工作的。。。

声明 Vuex 存储时,需要创建三个主要属性,statemutationsactions。这些属性作为单个结构,通过$store注入的原型或导出的store变量绑定到 Vue 应用。

state是一个集中的对象,它保存您的信息,并使其可供mutationactions,或组件使用。更改state始终需要通过mutation执行同步功能。

mutation是一个同步函数,可以更改state并且是可跟踪的,因此在开发时,您可以在 Vuex 存储中穿越所有执行的mutations

action是一个异步函数,可以用来保存业务逻辑、API 调用、调度其他actions和执行mutations。这些功能是 Vuex 商店中任何更改的共同入口点。

在此图表中可以看到 Vuex 存储的简单表示:

另见

有关 Vuex 的更多信息,请访问https://vuex.vuejs.org/

创建和理解 Vuex 状态

Vuex 状态似乎很容易理解。然而,随着数据变得更加深入和嵌套,其复杂性和可维护性可能会变得更加复杂。

在本教程中,我们将学习如何创建一个 Vuex 状态,该状态可以在渐进式 Web 应用(PWA)/单页应用(SPA)服务器端呈现(SSR)的场景中使用,没有任何问题。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

为了启动我们的组件,我们将使用 Vue 项目和 Vue CLI,这是我们在第 6 章中的延迟异步加载页面配方中使用的,或者我们可以启动一个新的 Vue 路由管理路由。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,根据需要添加RouterVuex功能,如“如何操作”中所示。。。C制作简单 Vuex 商店配方的部分。

我们的配方将分为两部分:

  • 通过vue ui添加 Vuex
  • 创建Vuex状态

让我们开始吧。

通过 UI 视图添加 Vuex

导入通过 Vue CLI 创建的旧项目时,可以通过vue ui界面自动添加 Vuex,而无需任何努力。我们将学习如何将 Vuex 库添加到旧项目中,以便继续开发配方。

在以下步骤中,我们将添加带有vue ui接口的 Vuex:

  1. 在项目文件夹中,通过在终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)上执行以下命令打开vue ui
> vue ui
  1. 选择您正在处理的正确项目。在右侧边栏中,单击插件菜单图标:

  1. 在插件页面的顶部栏上,单击添加 vuex 按钮。这将触发一个弹出模式,然后单击继续按钮完成 Vuex 在应用上的安装:

  1. 将 Vuex 添加到我们的应用将改变应用的结构。首先,我们会注意到在src文件夹中有一个名为store的新文件夹,在main.js文件中,它被添加到 Vue 应用中store的导入和注入中:
import './server/server';
import Vue from 'vue';
import App from './App.vue';
import Vuesax from 'vuesax';
import './style.css';
import router from './router'
import store from './store'

Vue.use(Vuesax);

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app');

创建 Vuex 状态

为了在 Vuex 中保存数据,您需要有一个初始状态,该状态随应用一起加载,并在用户进入应用时定义为默认状态。在这里,我们将学习如何创建 Vuex 状态并将其用作单例,以便 Vuex 可以在 SPA 和 SSR 页面中使用:

现在,我们将创建一个可用于 SSR 和 SPA 的 Vuex 商店:

  1. src/store文件夹中创建一个名为user的新文件夹,并在该文件夹中创建一个名为state.js的新文件。
  2. 创建一个新的generateState函数。此函数将返回一个 JavaScript 对象,该对象具有三个主要属性:dataloadingerrordata属性将是一个 JavaScript 对象,一个名为usersList的属性定义为空数组作为默认值,一个名为userData的属性定义为用户的默认对象。loading属性将是一个布尔值,默认设置为false,并且error将有一个默认值初始化为null
const generateState = () => ({
  data: {
    usersList: [],
    userData: {
      name: '',
      email: '',
      birthday: '',
      country: '',
      phone: '',
    },
  },
  loading: false,
  error: null,
});
  1. 创建函数后,我们将在文件末尾创建一个export default对象,它将是一个 JavaScript 对象,我们将破坏generateState函数的返回:
export default { ...generateState() };
  1. user文件夹中创建一个名为index.js的新文件并打开它。
  2. 导入新创建的state
import state from './state';
  1. 在文件末尾,创建一个export default文件作为 JavaScript 对象。在此对象中,我们将添加导入的state
export default {
  state,
};
  1. src/store文件夹中打开index.js文件。
  2. user文件夹导入index.js文件:
import Vue from 'vue';
import Vuex from 'vuex';
import UserStore from './user';
  1. 在创建新 Vuex 存储的export default函数中,我们将删除其中的所有属性,并将导入的UserStore解构对象放入Vuex.Store参数中:
export default new Vuex.Store({
  ...UserStore,
})

它是如何工作的。。。

当使用vue ui添加 Vuex 作为插件时,vue ui会自动添加所需文件,并导入所需的所有内容。这是 Vuexstore创建的初始阶段。

首先是创建一个专用文件来管理state,我们可以使用该文件将状态如何开始以及如何初始化的过程与store分开。

在这个state的例子中,我们使用了一个函数,每次调用它时都会生成一个全新的state。这是一个很好的实践,因为在 SSR 环境中,服务器的state总是相同的,我们需要为每个新连接创建一个新的state

创建state后,我们需要创建默认文件,用于导出将在user文件夹中创建的 Vuex 文件。此文件是将在文件夹stateactionsmutationgetters中创建的所有文件的简单导入。导入后,我们导出一个具有所需 Vuex 属性名称的对象,stateactionsmutationsgetters

最后,在 Vuexstore中,我们导入聚合所有内容的文件,并将其解构到我们的存储中进行初始化。

还有更多。。。

Vuex状态在应用中是唯一的真实来源,它的工作方式类似于全局数据管理器,不应直接更改。这是因为我们需要防止数据的突变与相同数据的并发突变。为了避免这种情况,我们总是需要通过突变来改变我们的状态,因为这些功能是同步的,并且由 Vuex 控制。

另见

有关 Vuex 状态的更多信息,请访问https://vuex.vuejs.org/guide/state.html

创建和理解 Vuex 突变

当 Vuex 中发生更改时,我们需要一种以异步形式执行此更改并跟踪它的方法,以便在第一次更改完成之前不会执行另一次更改。

对于这种情况,我们需要突变,突变是只负责更改应用状态的函数。

在本食谱中,我们将学习如何创建 Vuex 突变以及最佳实践。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要启动我们的组件,我们将使用我们在“创建和理解 Vuex 状态配方中使用的 Vue 项目和 Vue CLI,或者我们可以启动一个新的项目。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,根据需要添加RouterVuex功能,如“如何操作”中所示。。。创建简单 Vuex 商店配方的部分。

现在我们为突变创建 Vuex 突变和碱基类型:

  1. src/store文件夹内的user文件夹中创建一个名为types.js的新文件,并将其打开。
  2. 在这个文件中,我们将创建一个export defaultJavaScript 对象,其中包含一组键,这些键将是我们的变体的名称。这些钥匙将是LOADINGERRORSET_USER_LISTSET_USER_DATAUPDATE_USERREMOVE_USER
export default {
  LOADING: 'LOADING',
  ERROR: 'ERROR',
  SET_USER_LIST: 'SET_USER_LIST',
  SET_USER_DATA: 'SET_USER_DATA',
  UPDATE_USER: 'UPDATE_USER',
  REMOVE_USER: 'REMOVE_USER',
}
  1. user文件夹中创建一个名为mutations.js的新文件,并将其打开。
  2. 导入新创建的types.js文件:
import MT from './types';
  1. 创建一个名为setLoading的新函数,该函数将接收 Vuexstate作为参数,并在执行时将状态的加载属性定义为true
const setLoading = state => {
  state.loading = true;
};
  1. 创建一个名为setError的新函数,该函数将接收 Vuexstate作为参数和payload。此函数将stateloading属性设置为false,将error属性设置为接收到的payload参数:
const setError = (state, payload) => {
  state.loading = false;
  state.error = payload;
};
  1. 创建一个名为setUserList的新函数,该函数将接收 Vuexstatepayload作为参数。此函数将为接收到的payload参数定义state.datausersList属性,将stateloading属性设置为false,将error属性设置为null
const setUserList = (state, payload) => {
  state.data.usersList = payload;
  state.loading = false;
  state.error = null;
};
  1. 创建一个名为setUserData的新函数,该函数将接收 Vuexstatepayload作为参数。此函数将为接收到的payload参数定义state.datauserData属性,将stateloading属性设置为false,将error属性设置为null
const setUserData = (state, payload) => {
  state.data.userData = payload;
  state.loading = false;
  state.error = null;
};
  1. 创建一个名为updateUser的新函数,该函数将接收 Vuexstatepayload作为参数。此功能将更新state.datausersList属性中的用户数据,将stateloading属性定义为false,将error属性定义为null
const updateUser = (state, payload) => {
  const userIndex = state.data.usersList.findIndex(u => u.id === 
     payload.id);
  if (userIndex > -1) {
    state.data.usersList[userIndex] = payload;
  }
  state.loading = false;
  state.error = null;
};
  1. 创建一个名为removeUser的新函数,该函数将接收 Vuexstatepayload作为参数。此功能将删除state.datausersList属性中的用户数据,将stateloading属性定义为false,将error属性定义为null
const removeUser = (state, payload) => {
  const userIndex = state.data.usersList.findIndex(u => u.id === 
     payload);
  if (userIndex > -1) {
    state.data.usersList.splice(userIndex, 1);
  }
  state.loading = false;
  state.error = null;
};
  1. 最后,创建一个export default对象,键是我们在types.js文件中创建的类型,并定义我们创建的函数的每个键:
export default {
  [MT.LOADING]: setLoading,
  [MT.ERROR]: setError,
  [MT.SET_USER_LIST]: setUserList,
  [MT.SET_USER_DATA]: setUserData,
  [MT.UPDATE_USER]: updateUser,
  [MT.REMOVE_USER]: removeUser,
}
  1. 打开user文件夹中的index.js文件。
  2. 导入新创建的mutations.js文件,并将其添加到export defaultJavaScript 对象:
import state from './state';
import mutations from './mutations';

export default {
  state,
  mutations,
};

它是如何工作的。。。

每个mutation都是一个函数,将作为commit调用,并在 Vuex 存储中具有标识符。此标识符是导出的 JavaScript 对象中的mutation键。在这个配方中,我们创建了一个文件,它将所有标识符保存为对象值,以便在代码中用作常量。

这个模式帮助我们开发 Vuexactions,它需要知道每个mutation名称。

导出mutationJavaScript 对象时,我们使用常量作为键,相应的函数作为其值,因此 Vuex 存储在调用时可以执行正确的函数。

另见

中查找有关 Vuex 突变的更多信息 https://vuex.vuejs.org/guide/mutations.html

创建和理解 Vuex getter

Vuex访问数据可以通过状态本身完成,这可能非常危险,也可以通过 getter 完成。getter 类似于可以预处理和交付的数据,而无需接触或干扰 Vuex 存储状态。

getters 背后的全部思想是,可以编写自定义函数,在需要时在单个位置从状态中提取数据,以便只获取所需的数据。

在本教程中,我们将学习如何创建可作为高阶函数使用的 Vuex getter 和动态 getter。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要启动我们的组件,我们将使用 Vue 项目和 Vue CLI,这是我们在“创建和理解 Vuex 突变”配方中使用的,或者我们可以启动一个新的 Vue 项目。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,并根据需要添加路由和Vuex功能,如“创建简单 Vuex 存储”配方的“如何操作…”部分所示。

在以下步骤中,我们将创建 Vuex getter:

  1. src/store/user文件夹中创建一个名为getters.js的新文件。
  2. 创建一个名为getUsersList的新函数,并返回state.data.usersList属性:
function getUsersList(state) {
  return state.data.usersList;
}

In a getter function, the first argument that the function will receive will be always the current state of the Vuex store.

  1. 创建一个名为getUserData的新函数,并返回state.data.userData属性:
function getUserData(state) {
  return state.data.userData;
}
  1. 创建一个名为getUserById的新函数,并返回另一个接收userId作为参数的函数。此返回功能将返回与接收到的userId相同的id匹配的state.data.usersList搜索结果:
function getUserById(state) {
  return (userId) => {
    return state.data.usersList.find(u => u.id === userId);
  }
}
  1. 创建一个名为isLoading的新函数,并返回state.loading属性:
function isLoading(state) {
  return state.loading;
}
  1. 创建一个名为hasError的新函数,并返回state.error属性:
function hasError(state) {
  return state.error;
}
  1. 最后,创建一个export defaultJavaScript 对象,将所有创建的函数作为属性:
export default {
  getUsersList,
  getUserData,
  getUserById,
  isLoading,
  hasError,
};
  1. 打开src/store/user文件夹中的index.js文件。
  2. 导入新创建的getters.js文件,并将其添加到导出默认 JavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';

export default {
  state,
  mutations,
  getters,
};

它是如何工作的。。。

getter 类似于对象的 GET 函数,是静态缓存函数–它们仅在[T0]发生更改时更改返回值。但是,如果将返回作为一个高阶函数添加,则可以赋予它更大的能力来使用更复杂的算法并提供特定的数据。

在这个配方中,我们创建了两种类型的 getter:最基本的 getter,具有简单的数据返回;高阶函数,需要作为函数调用以检索所需的值。

还有更多。。。

在业务逻辑中使用 getter 是收集更多状态数据的好方法。这是一个很好的模式,因为在大型项目中,它可以帮助其他开发人员更多地了解每个 GET 函数中发生的事情以及它在幕后是如何工作的。

您始终需要记住,getter 是同步函数,并且对状态更改作出反应,因此 getter 上的数据将被存储和缓存,直到单一真相源接收到提交并更改它。

另见

您可以在上找到有关 Vuex Getter 的更多信息 https://vuex.vuejs.org/guide/getters.html

创建和理解 Vuex 操作

您已经准备好了所有状态和数据集,现在您需要从外部源获取新数据或在应用中更改这些数据。下面是行动发挥作用的部分。

操作负责在应用和外部世界之间的通信中协调流程。控制何时需要在状态上更改数据并将其返回给操作的调用方。

通常,动作是通过组件或视图进行的分派,但在某些情况下,动作可以分派另一个动作以在应用中创建动作链。

在本教程中,我们将学习如何在应用中创建定义用户列表、更新用户和删除用户所需的操作。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要启动我们的组件,我们将使用我们在“创建和理解 Vuex getters配方中使用的 Vue 项目和 Vue CLI,或者我们可以启动一个新的项目。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,并根据需要添加RouterVuex功能,如“如何操作”中所示。。。创建简单 Vuex 商店配方的部分。

现在按照以下步骤创建 Vuex 操作:

  1. src/store/user文件夹中创建一个名为actions.js的新文件。
  2. fetchApi包装器导入突变类型文件(types.js),以及getHttppatchHttppostHttpdeleteHttp函数:
import {
  getHttp,
  patchHttp,
  deleteHttp,
  postHttp,
} from '@/http/fetchApi';
import MT from './types';
  1. 创建一个名为createUser的新asynchronous函数,该函数接收一个解构的 JavaScript 对象作为第一个参数,该对象具有commit属性,userData作为第二个参数,用于创建用户。在try上下文中添加try/catch语句。首先,我们执行commit(MT.LOADING),然后从 API 中获取用户列表,最后,commit(MT.SET_USER_DATA, data),传递要修改的用户列表。如果我们收到异常并进入Catch语句,我们将执行commit(MT.ERROR, error),将我们收到的错误传递给state
async function createUser({ commit }, userData) {
  try {
    commit(MT.LOADING);
    await postHttp(`/api/users`, {
      data: {
        ...userData,
      }
    });
    commit(MT.SET_USER_DATA, userData);
  } catch (error) {
    commit(MT.ERROR, error);
  }
}
  1. 创建一个名为fetchUsersList的新asynchronous函数,该函数接收一个解构的 JavaScript 对象作为第一个参数,该对象具有commit属性。在try上下文中添加try/catch语句。我们执行commit(MT.LOADING),然后从 API 中获取用户列表,最后commit(MT.SET_USER_LIST, data),传递要修改的用户列表。如果我们收到异常并进入catch语句,我们将执行一个commit(MT.ERROR, error)突变,将我们收到的错误传递给state
async function fetchUsersList({ commit }) {
  try {
    commit(MT.LOADING);
    const { data } = await getHttp(`api/users`);
    commit(MT.SET_USER_LIST, data);
  } catch (error) {
    commit(MT.ERROR, error);
  }
}
  1. 创建一个名为fetchUsersData的新asynchronous函数,该函数接收一个解构的 JavaScript 对象作为第一个参数,该对象具有commit属性,第二个参数为将要获取的userId。在try上下文中添加try/catch语句。我们执行commit(MT.LOADING),然后从 API 中获取用户列表,最后,commit(MT.SET_USER_DATA, data),传递要修改的用户列表。如果我们收到异常并进入catch语句,我们将执行commit(MT.ERROR, error)的突变,将我们收到的错误传递给state
async function fetchUserData({ commit }, userId) {
  try {
    commit(MT.LOADING);
    const { data } = await getHttp(`api/users/${userId}`);
    commit(MT.SET_USER_DATA, data);
  } catch (error) {
    commit(MT.ERROR, error);
  }
}
  1. 创建一个名为updateUser的新asynchronous函数,该函数接收一个解构的 JavaScript 对象作为第一个参数,该对象具有commit属性,payload作为第二个参数。在try上下文中添加try/catch语句。我们执行commit(MT.LOADING),然后将用户数据修补到 API,最后执行commit(MT.UPDATE_USER, payload),传递给用户新的数据进行变异。如果我们收到一个异常并进入catch语句,我们将执行一个commit(MT.ERROR, error)突变,将我们收到的错误传递给state
async function updateUser({ commit }, payload) {
  try {
    commit(MT.LOADING);
    await patchHttp(`api/users/${payload.id}`, {
      data: {
        ...payload,
      }
    });
    commit(MT.UPDATE_USER, payload);
  } catch (error) {
    commit(MT.ERROR, error);
  }
}
  1. 创建一个名为removeUser的新asynchronous函数,该函数接收一个解构的 JavaScript 对象作为第一个参数,该对象具有commit属性,userId作为第二个参数。在try上下文中添加try/catch语句。我们执行commit(MT.LOADING),然后从 API 中删除用户数据,最后执行commit(MT.REMOVE_USER, userId),传递userId用于突变。如果我们收到异常并进入Catch语句,我们将执行一个commit(MT.ERROR, error)突变,将我们收到的错误传递给state
async function removeUser({ commit }, userId) {
  try {
    commit(MT.LOADING);
    await deleteHttp(`api/users/${userId}`);
    commit(MT.REMOVE_USER, userId);
  } catch (error) {
    commit(MT.ERROR, error);
  }
}
  1. 最后,我们将创建一个导出默认 JavaScript 对象,将所有创建的函数作为属性:
export default {
  createUser,
  fetchUsersList,
  fetchUserData,
  updateUser,
  removeUser,
} 
  1. src/store/user文件夹的index.js中导入新创建的actions.js文件,并将其添加到export defaultJavaScript 对象中:
import state from './state';
import mutations from './mutations';
import getters from './getters';
import actions from './actions';

export default {
  state,
  mutations,
  getters,
  actions,
};

它是如何工作的。。。

操作是所有 Vuex 生命周期更改的初始值设定项。在调度时,该操作可以执行一个变种提交、另一个操作调度,甚至可以执行对服务器的 API 调用。

在我们的例子中,我们进行 API 调用并将其放入操作中,因此当异步函数返回时,我们可以执行提交并将状态设置为函数的结果。

另见

有关 Vuex 操作的更多信息,请访问https://vuex.vuejs.org/guide/actions.html

使用 Vuex 创建动态组件

将 Vuex 与 Vue 组件相结合,可以在不需要直接父子通信的情况下利用多个组件之间的反应性,并划分组件的责任。

使用此方法允许开发人员增强应用的规模,不需要将数据状态存储在组件本身中,而是使用单个真实源作为整个应用的存储。

在这个配方中,我们将使用最后的配方来改进一个应用,在这个应用中,它使用了亲子沟通,并使之成为整个应用中可用的唯一真理来源。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

为了创建我们的动态组件,我们将把组件从有状态转换为无状态,并提取一些可以制作成新组件的部分。

我们将使用 Vue 项目和我们在“[T0]创建和理解 Vuex 操作[T1]”配方中使用的 Vue CLI,或者我们可以启动一个新的 Vue 项目。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,并根据需要添加RouterVuex功能,如“如何操作”中所示。。。创建简单 Vuex 商店配方的部分。

我们的配方将分为五个部分:

  • 创建用户列表组件
  • 编辑用户列表页面
  • 编辑用户视图页面
  • 编辑用户视图页面
  • 编辑用户创建页面

让我们开始吧。

创建用户列表组件

由于 Vuex 使我们能够在应用上获得单一的真实来源,因此我们可以为应用创建一个新组件,该组件将处理用户列表并触发 Vuex 操作,从服务器获取用户列表。该组件可以是无状态的,并且可以自己执行Vuex操作。

单文件组件

让我们对单文件组件的<script>部分进行编码:

  1. src/components文件夹中创建一个名为userList.vue的新文件。
  2. src/mixin文件夹导入changeRouterMixin
import changeRouteMixin from '@/mixin/changeRoute';
  1. 创建一个export defaultJavaScript 对象,并添加一个名为mixin的新 Vue 属性,默认值为数组。在此数组中,添加导入的changeRouteMixin
mixins: [changeRouteMixin],
  1. 创建名为computed的新 Vue 属性。在此属性中,创建一个名为userList的新值。此属性将是一个返回 Vuex 存储 gettergetUsersList的函数:
computed: {
  userList() {
    return this.$store.getters.getUsersList;
  },
},

单文件组件

在这里,我们将对单文件组件的<template>部分进行编码:

  1. 打开views文件夹内users文件夹中的List.vue文件,复制VsTable组件中的内容和组件。
  2. src/components文件夹中打开userList.vue文件。
  3. 将您从List.vue文件复制的内容粘贴到<template>部分:
<template>
  <vs-table
    :data="userList"
    search
    stripe
    pagination
    max-items="10"
    style="width: 100%; padding: 20px;"
  >
    <template slot="thead">
      <vs-th sort-key="name">
        #
      </vs-th>
      <vs-th sort-key="name">
        Name
      </vs-th>
      <vs-th sort-key="email">
        Email
      </vs-th>
      <vs-th sort-key="country">
        Country
      </vs-th>
      <vs-th sort-key="phone">
        Phone
      </vs-th>
      <vs-th sort-key="Birthday">
        Birthday
      </vs-th>
      <vs-th>
        Actions
      </vs-th>
    </template>
    <template slot-scope="{data}">
      <vs-tr :key="index" v-for="(tr, index) in data">
        <vs-td :data="data[index].id">
          {{data[index].id}}
        </vs-td>
        <vs-td :data="data[index].name">
          {{data[index].name}}
        </vs-td>
        <vs-td :data="data[index].email">
          <a :href="`mailto:${data[index].email}`">
            {{data[index].email}}
          </a>
        </vs-td>
        <vs-td :data="data[index].country">
          {{data[index].country}}
        </vs-td>
        <vs-td :data="data[index].phone">
          {{data[index].phone}}
        </vs-td>
        <vs-td :data="data[index].birthday">
          {{data[index].birthday}}
        </vs-td>
        <vs-td :data="data[index].id">
          <vs-button
            color="primary"
            type="filled"
            icon="remove_red_eye"
            size="small"
            @click="changeRoute('view', data[index].id)"
          />
          <vs-button
            color="success"
            type="filled"
            icon="edit"
            size="small"
            @click="changeRoute('edit', data[index].id)"
          />
          <vs-button
            color="danger"
            type="filled"
            icon="delete"
            size="small"
            @click="deleteUser(data[index].id)"
          />
        </vs-td>
      </vs-tr>
    </template>
  </vs-table>
</template>

编辑用户列表页面

现在,我们已经将用户列表提取到一个新组件中,我们需要导入这个组件并删除扰乱视图的旧 VsTable。

单文件组件

在这一步中,我们将编写单文件组件的<script>部分:

  1. 打开views文件夹中users文件夹中的List.vue文件。
  2. components文件夹导入新创建的用户列表组件:
import changeRouteMixin from '@/mixin/changeRoute';
import UserTableList from '@/components/userList';
  1. export defaultJavaScript 对象中,添加一个名为components的新属性。将属性声明为 JavaScript 对象,并将导入的UserTableList组件添加到该对象:
components: { UserTableList },
  1. methods属性中,在getAllUsers函数中,我们需要更改内容以在调用时执行 Vuex 分派。此方法将执行fetchUsersListVuex 操作:
async getAllUsers() {
  this.$store.dispatch('fetchUsersList');
},
  1. 最后,在deleteUser函数中,我们需要更改内容,以便在调用时执行 Vuex 分派。此方法将执行removeUserVuex 操作,将userId作为参数传递:
async deleteUser(id) {
  this.$store.dispatch('removeUser', id);
  await this.getAllUsers();
},

单文件组件

让我们对单文件组件的<template>部分进行编码:

  1. 打开view文件夹中users文件夹中的List.vue文件。
  2. 用新进口的UserTableList替换VsTable部件及其内含物:
<vs-col
  vs-type="flex"
  vs-justify="left"
  vs-align="left"
  vs-w="12">
  <user-table-list />
</vs-col>

编辑用户视图页面

现在,我们可以将 Vuex 添加到用户视图页面。我们将添加 Vuex 操作和 getter 来操作数据,并从页面中提取管理数据的责任。

单文件组件

现在您要创建单文件组件的<script>部分:

  1. view文件夹中的users文件夹中打开View.vue文件。
  2. 删除 Vuedata属性。
  3. 在 Vuecomputed属性中,添加userData,返回 Vuex 吸气剂getUserData
userData() {
  return this.$store.getters.getUserData;
},
  1. 最后,在getUserById方法中,将内容更改为分派 Vuex 操作fetchUserData,将计算出的userId属性作为参数传递:
async getUserById() {
  await this.$store.dispatch('fetchUserData', this.userId);
},

单文件组件

现在是编写单文件组件的<template>部分的时候了:

  1. 打开view文件夹中users文件夹中的View.vue文件。
  2. 在 UserForm 组件中,将v-model指令更改为:value指令:
<user-form
  :value="userData"
  disabled
/>

When using a read-only value, or you need to remove the syntactic sugar of the v-model directive, you can declare the input value as a :value directive and the value change event to an @input event listener.

编辑用户编辑页面

我们需要编辑我们的用户。在上一个配方中,我们使用了一个有状态的页面,并执行页面中的所有内容。我们将把状态转换为临时状态,并对 Vuex 操作执行 API 调用。

单文件组件

在这里,我们将创建单文件组件的<script>部分:

  1. 打开view文件夹中users文件夹中的Edit.vue文件。
  2. 在 Vuedata属性中,将数据的名称从userData更改为tmpUserData
data: () => ({
  tmpUserData: {
    name: '',
    email: '',
    birthday: '',
    country: '',
    phone: '',
  },
}),
  1. 在 Vuecomputed属性中,添加一个名为userData的新属性,该属性将返回 Vuex gettergetUserData
userData() {
  return this.$store.getters.getUserData;
}
  1. 添加一个名为watch的新 Vue 属性,并添加一个新属性userData,该属性将是一个 JavaScript 对象。在此对象中,添加三个属性,handlerimmediatedeephandler属性将是一个函数,它接收一个名为newData的参数,该参数将为此参数设置tmpUserDataimmediatedeep属性都是设置为true的布尔属性:
watch: {
  userData: {
    handler(newData) {
      this.tmpUserData = newData;
    },
    immediate: true,
    deep: true,
  }
},
  1. 在 Vuemethods属性中,我们需要更改getUserById的内容,以发送名为fetchUserData的 Vuex 动作,并将computed属性userId作为参数传递:
async getUserById() {
  await this.$store.dispatch('fetchUserData', this.userId);
},
  1. updateUser方法中,将内容更改为分派名为updateUser的 Vuex 动作,并将tmpUserData作为参数传递:
async updateUser() {
  await this.$store.dispatch('updateUser', this.tmpUserData);
  this.changeRoute('list');
},

单文件组件

在这一部分中,我们将编写单文件组件的<template>部分:

  1. 打开view文件夹内users文件夹中的Edit.vue
  2. UserForm组件的v-model指令目标更改为tmpUserData
<vs-col
  vs-type="flex"
  vs-justify="left"
  vs-align="left"
  vs-w="12"
  style="margin: 20px"
>
  <user-form
    v-model="tmpUserData"
  />
</vs-col>

编辑用户创建页面

对于用户创建页面,更改将是最小的,因为它只执行一个 API 调用。我们需要添加 Vuex 操作分派。

单文件组件

在这里,我们将创建单文件组件的<script>部分:

  1. 打开view文件夹中users文件夹中的Create.vue文件。
  2. createUser方法的内容更改为发送一个名为createUser的 Vuex 动作,传递userData作为参数:
async createUser() {
  await this.$store.dispatch('createUser', this.userData);
  this.changeRoute('list');
},

它是如何工作的。。。

在所有四个页面中,我们都进行了更改,删除了从页面到 Vuex 存储的业务逻辑或 API 调用,并尝试降低其维护数据的责任。

因此,我们可以将一段代码放入一个新组件中,该组件可以放置在应用中的任何位置,并将显示当前用户列表,而不受实例化它的容器的任何限制。

这种模式有助于我们开发更突出的应用,在这些应用中,需要的组件不太面向业务,而是更专注于它们的任务。

另见

有关 Vuex 应用结构的更多信息,请访问https://vuex.vuejs.org/guide/structure.html

为开发添加热模块重新加载

热模块重新加载HMR是一种用于加快应用开发速度的技术,您无需刷新整个页面即可在编辑器上获得刚刚更改的新代码。HMR 将仅更改和刷新您在编辑器上更新的零件。

在所有 Vue CLI 项目或基于 Vue 的框架(如 Quasar Framework)中,HMR 都出现在应用的演示文稿中。因此,每当您更改任何作为 Vue 组件的文件并对其进行渲染时,应用都会动态地将旧代码替换为新代码。

在本教程中,我们将学习如何将 HMR 添加到 Vuex 存储,并能够在不刷新整个应用的情况下更改 Vuex 存储。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要启动我们的组件,我们将使用 Vue 项目和 Vue CLI,这是我们在“[T0]使用 Vuex[T1]创建动态组件”配方中使用的,或者我们可以启动一个新的。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,根据需要添加RouterVuex功能,如“如何操作”中所示。。。创建简单 Vuex 商店配方的部分。

在以下步骤中,我们将向 Vuex 添加 HMR:

  1. 打开src/store文件夹中的index.js文件。
  2. export default转换为一个名为store的常数,并使其可导出:
export const store = new Vuex.Store({
  ...UserStore,
});
  1. 检查网页包hot-module-reload插件是否处于活动状态:
if (module.hot) {}
  1. 创建一个名为hmr的新常量,它是一个数组,包含指向user文件夹的index.jsgetters.jsactions.jsmutations.js文件的方向:
const hmr = [
  './user',
  './user/getters',
  './user/actions',
  './user/mutations',
];
  1. 创建一个名为reloadCallback的新函数。在此函数中,创建三个常量,gettersactionsmutations。每个常量将指向user文件夹中的等效文件,并调用store.hotUpdate函数,将对象作为参数传递给您创建的常量值:
const reloadCallback = () => {
  const getters = require('./user/getters').default;
  const actions = require('./user/actions').default;
  const mutations =  require('./user/mutations').default;

  store.hotUpdate({
    getters,
    actions,
    mutations,
  })
};

Because of the Babel output of the files, you need to add the .default in the end of the files that you are dynamically importing with the webpack require function.

  1. 执行 webpack HMRaccept函数,将hmr常量作为第一个参数传递,将reloadCallback作为第二个参数传递:
module.hot.accept(hmr, reloadCallback);
  1. 最后,默认导出创建的store
export default store;

它是如何工作的。。。

Vuex 商店通过 webpack HMR 插件的 API 支持 HMR。

当它可用时,我们会创建一个可以更新的可能文件列表,这样 webpack 就可以知道这些文件的任何更新。更新这些文件时,将执行您创建的特殊回调。此回调使 Vuex 能够完全更新或更改已更新文件的行为。

另见

有关 Vuex 热重新加载的更多信息,请访问https://vuex.vuejs.org/guide/hot-reload.html

有关网页 HMR 的更多信息,请访问https://webpack.js.org/guides/hot-module-replacement/

创建 Vuex 模块

随着应用的增长,在单个对象中工作可能会非常危险。项目的可维护性和每次变更可能产生的风险每次都变得更糟。

Vuex 有一种称为模块的方法,可以帮助我们将商店划分为不同的商店分支。这些分支或模块中的每一个都有一组不同的状态、突变、getter 和动作。此模式有助于开发并降低向应用添加新功能的风险。

在这个配方中,我们将学习如何创建一个模块,以及如何使用它,将其划分为专用的分支。

准备

此配方的先决条件如下:

  • Node.js 12+

所需的 Node.js 全局对象如下:

  • @vue/cli
  • @vue/cli-service-global

怎么做。。。

要启动我们的组件,我们将使用 Vue 项目和 Vue CLI,这是我们在“[T0]使用 Vuex[T1]创建动态组件”配方中使用的,或者我们可以启动一个新的。

要启动新的,请打开终端(macOS 或 Linux)或命令提示符/PowerShell(Windows)并执行以下命令:

> vue create vuex-store

选择手动功能,并根据需要添加RouterVuex功能,如“如何操作”中所示。。。创建简单 Vuex 商店配方的部分。

我们的配方将分为两部分:

  • 创建新的身份验证模块
  • 向 Vuex 添加模块

让我们开始吧。

创建新的身份验证模块

首先,我们需要创建一个新的Vuex模块。此示例模块将被称为authentication,并将为用户存储凭据数据。

在这些步骤中,我们将为Vuex创建authentication模块:

  1. src/store文件夹中创建一个名为authentication的新文件夹。
  2. 在这个新创建的文件夹中,创建一个名为state.js的新文件,并将其打开。
  3. 创建一个名为generateState的函数,该函数将返回一个属性为data.usernamedata.tokendata.expiresAtloadingerror的 JavaScript 对象:
const generateState = () => ({
  data: {
    username: '',
    token: '',
    expiresAt: null,
  },
  loading: false,
  error: null,
});
  1. 在文件末尾创建一个export default对象。此对象将是一个 JavaScript 对象。我们将破坏generateState函数的返回:
export default { ...generateState() };
  1. src/store文件夹内的authentication文件夹中创建一个名为index.js的新文件,并将其打开。

  2. 导入新创建的state.js文件:

import state from './state';
  1. 在文件末尾创建一个export default对象。此对象将是一个 JavaScript 对象。添加一个名为namespaced的新属性,其值设置为true,并添加导入的state
export default {
  namespaced: true,
  state,
};

将模块添加到 Vuex

现在我们已经创建了模块,我们将把它们添加到 Vuex 存储中。我们可以将新模块与旧代码集成。这不是问题,因为 Vuex 将使用完全独立的 Vuex 存储将新模块作为命名空间对象处理。

现在,在这些步骤中,我们将向 Vuex 添加创建的模块:

  1. 打开src/store文件夹中的index.js文件。
  2. authentication文件夹导入index.js文件:
import Vue from 'vue';
import Vuex from 'vuex';
import UserStore from './user';
import Authentication from './authentication';
  1. Vuex.Store函数中,添加一个名为modules的新属性,它是一个 JavaScript 对象。然后添加导入的UserAuthentication模块:
export default new Vuex.Store({
  ...UserStore,
  modules: {
    Authentication,
  }
})

它是如何工作的。。。

模块的工作方式类似于单独的 Vuex 存储,但位于同一 Vuex 单一真相来源中。这有助于开发更大规模的应用,因为您可以维护和使用更复杂的结构,而无需检查同一文件中的问题。

同时,还可以使用模块和普通 Vuex 存储,从遗留应用迁移,这样您就不必从头重写所有内容,就可以使用模块结构。

在我们的例子中,我们添加了一个名为authentication的新模块,其中存储中只存在一个状态,并继续使用旧用户 Vuex 存储,以便将来我们可以将用户存储重构为一个新模块,并将其分离为一个更具体的域驱动体系结构。

另见

有关 Vuex 模块的更多信息,请访问https://vuex.vuejs.org/guide/modules.html