如果你正在构建一个中等规模的SPA,你有可能会遇到想要更好地处理你的Vue组件的状态的情况。
在任何应用程序中,多个组件都依赖于同一块状态。让我们想象一下,来自不同组件的多个动作都想突变同一个状态。为了克服这些挑战,Vuex帮助我们维护整个应用程序的状态。
在这篇文章中,我将指导你在TypeScript中实现一个Vuex模块,然后用Jest对其进行单元测试。本教程的完整代码可在vuex-test的GitHub仓库中找到,请随时分叉。让我们开始吧!
什么是Vuex?
Vuex是Vue应用程序的状态管理模式和库,允许你在你的应用程序中使用集中的状态管理,帮助你利用类似Flux的架构。Vuex存储包含四个核心概念:
- 状态
- 获取器
- 变异
- 行动
状态对象包含你想在存储中拥有的数据,包括你所有的应用程序级别的状态,作为单一的真理来源。状态中定义的属性可以是任何数据类型,包括字符串、数字、对象或数组。
如果你想有一个基于存储状态的派生状态,例如,计算项目列表,过滤集合,或在其他模块或组件中使用相同的派生状态集,你可以定义获取器。
另一方面,突变是我们改变状态的唯一途径。突变总是同步的,而且有效载荷是可选的。你可以通过提交来调用突变,即MUTATION_NAME 或payload 。我们总是建议从动作中调用突变。
动作可以执行异步操作并提交突变。动作处理程序接收一个上下文对象,该对象在存储实例上暴露了相同的方法或属性集。
你可以使用context.getters 和context.state 来获取状态,并使用context.commit 来调用突变。你可以使用action-name 和payload 来调用动作处理程序,它们可以从商店内的其他动作中调用:

Vuex架构
创建一个Vuex模块
随着你的应用程序规模的增加,你的商店会变得臃肿。为了防止这种情况,Vuex允许你将商店分割成模块。每个模块可以包含自己的状态、getters、突变和动作。
作为一个例子,让我们创建一个管理待办事项列表的应用程序。首先,创建一个新的待办事项操作模块,它负责获取所有的待办事项,并根据需要更新状态。
我们的目标是为中到大规模的应用建立模块,因此,最好将突变类型、被称为函数的操作和模块的实现分成不同的文件:
mutation-types.ts:包含所有的函数名称actions.ts:负责所有的异步操作index.ts:模块的实现
import { IToDo } from '@/types/todo';
import {Module, VuexModule, Mutation, Action} from 'vuex-module-decorators';
import TodoActions from './actions';
import * as mutationTypes from './mutation-types';
@Module({namespaced: true, name: "Todos"})
export class ToDoModule extends VuexModule {
todos:Array<IToDo> = [];
loading = false;
get completedTodos(){
return this.todos.filter((todo:IToDo)=> todo.completed);
}
@Mutation
[mutationTypes.ON_FETCH_TODOS_STARTED]() {
this.loading = true;
}
@Mutation
\[mutationTypes.ON_FETCH_TODOS_SUCCESS\](data: Array<IToDo>) {
this.loading = false;
this.todos = data;
}
@Mutation
[mutationTypes.ON_FETCH_TODOS_FAILED]() {
this.loading = false;
this.todos = [];
}
@Action({rawError: true})
public async fetchTodos():Promise<void> {
try {
this.context.commit(mutationTypes.ON_FETCH_TODOS_STARTED);
const response: Array<IToDo> = await TodoActions.fetchTodos();
this.context.commit(mutationTypes.ON_FETCH_TODOS_SUCCESS, response);
} catch (error) {
this.context.commit(mutationTypes.ON_FETCH_TODOS_FAILED);
}
}
}
上面的代码片段包含以下实现:
fetchTodos Action:从REST API中获取待办事项,并提交突变的内容ON_FETCH_TODOS_STARTED突变:更新 状态属性loadingON_FETCH_TODOS_SUCCESS突变:更新 状态数组todosON_FETCH_TODOS_FAILED突变:重置todos,并将loading更新为false。completedTodos获取器:只获取已完成的待办事项
初始化测试
我们将使用Jest框架进行单元测试;Jest只是一个JavaScript测试框架,可以很容易地用任何基于节点的包管理器安装,如npm或Yarn。使用Jest有一些优势,例如,Jest测试可以并行运行,包括内置的代码覆盖率,并支持隔离测试、嘲弄和快照测试。
你可以通过创建一个商店,将Vuex附加到Vue,并注册商店来初始化测试。localVue 是范围内的Vue构造函数,我们可以在不影响全局Vue构造函数的情况下改变它。下面的代码片断将初始化商店:
describe('Todos Module', function() {
let store: any;
let todosInstance: ToDoModule;
beforeEach(function() {
localVue.use(Vuex);
store = new Vuex.Store({});
registerStoreModules(store);
todosInstance = getModule(ToDoModule, store);
});
it('should exists', function() {
expect(todosInstance).toBeDefined();
});
});
测试动作
在todos 模块中,我们创建了fetchTodos 动作,它从REST API中获取数据并使用突变来填充状态。由于REST API是一个外部调用,我们可以使用Jest函数来模拟它,然后验证它是否被调用以及状态是否被更新:
it('fetchTodos action should fill todos state', async function() {
// arrange
const todosMocked = todos as Array<IToDo>;
// act
jest.spyOn(TodoActions, 'fetchTodos').mockImplementation(
(): Promise<Array<IToDo>> => {
return Promise.resolve(todosMocked);
}
);
await todosInstance.fetchTodos();
// assert
expect(todosInstance.todos.length >0).toEqual(true);
expect(TodoActions.fetchTodos).toHaveBeenCalled();
});
测试获取器
Getter函数只是返回状态对象。在我们的例子中,我们有一个getter函数,completedTodos ,它应该返回已经完成的to-do项目。
it('completedTodos getter should return only completed todos', async function() {
// arrange
const completedTodos = todosInstance.completedTodos;
// assert
expect(completedTodos.every((todo:IToDo)=> todo.completed)).toEqual(true);
});
测试突变
正如我们已经知道的,突变是改变状态的唯一途径。我们可以通过发送模拟待办事项并验证状态是否被修改来测试ON_FETCH_TODOS_SUCCESS 突变。
下面的代码片断是针对success 突变的。这也适用于started 和error 突变:
it('ON_FETCH_TODOS_SUCCESS mutation should update given todos', function() {
// arrange
const todosTest = [ { userId: 13, id: 12, title: "Move to new city", completed: false }, { userId: 15, id: 21, title: "Finish a novel", completed: true }, ];
// act
todosInstance.ON_FETCH_TODOS_SUCCESS(todosTest);
// assert
expect(todosInstance.todos.length).toEqual(2);
expect(todosInstance.todos).toEqual(todosTest);
});
结论
在本教程中,我们通过使用TypeScript和Jest创建和单元测试一个Vuex模块,了解了Vuex。我们涵盖了Vuex存储的四个核心概念,包括状态、获取器、突变和动作。通过Vuex的集中式状态管理,你可以简化你的应用程序,并利用类似Flux的架构优势。
我希望你能学到一些新东西,如果你有任何问题,请务必留言。编码愉快!