Разработка архитектуры управления состоянием в масштабном приложении Vue.js
Эта статья является третьей частью серии материалов о разработке архитектуры приложения Vue.js. Основной шаблон проекта доступен здесь. В этой статье я объясню, как создать архитектуру управления состоянием приложения.
Vue.js предоставляет собственный шаблон управления состоянием и библиотеку Vuex. Состояния бывает двух типов:
- Локальное состояние компонента.
- Состояние уровня приложения.
Как управлять огромным объемом данных?
Но приложение может быть большим и сложным. Поэтому оно должно управлять огромным объемом данных. Как организовать состояние приложения в этом случае? С помощью модулей.
В первой статье мы разбили приложение на несколько модулей. Аналогичным образом мы организуем состояние приложения на уровне функций. При этом каждый из модулей функций будет отвечать за управление связанными состояниями.
В структуре проекта у каждого модуля есть папка shared/state. В ней мы будем хранить все файлы состояния, связанные с этим модулем. Например, для модуля user файлы состояний будут храниться в users/shared/state (отдельный файл для каждого состояния). Пример такого файла:
import { reflectKeys } from '@/app/shared/services';
import { fetchUsers } from '../services';
/** Исходное состояние */
const initialState = {
loading: false,
data: null,
error: null
};
/** Префикс для типов мутаций и действий */
const namespacedPrefix = '[USERS]';
/**
* Типы мутаций
*/
const mutationTypes = reflectKeys(
[
'USERS_DATA_SUCCESS',
'USERS_DATA_REQUEST',
'USERS_DATA_ERROR',
'USERS_DATA_RESET'
],
namespacedPrefix
);
const {
USERS_DATA_ERROR,
USERS_DATA_REQUEST,
USERS_DATA_RESET,
USERS_DATA_SUCCESS
} = mutationTypes;
/**
* Мутации данных пользователей
*/
const mutations = {
/** запрос данных пользователей */
[USERS_DATA_REQUEST](state) {
Object.assign(state, { loading: true, error: null });
},
/** успешный ответ */
[USERS_DATA_SUCCESS](state, payload) {
Object.assign(state, { loading: false, data: payload });
},
/** ошибка */
[USERS_DATA_ERROR](state, payload) {
Object.assign(state, {
loading: false,
data: null,
error: payload || true
});
},
/** сброс данных пользователей */
[USERS_DATA_RESET](state) {
Object.assign(state, ...initialState);
}
};
/** Константы типов действий */
export const actionsTypes = reflectKeys(['FETCH_USER_DATA'], namespacedPrefix);
/**
* Действия с пользовательскими данными
*/
const actions = {
/** извлечение данных пользователей */
async [actionsTypes.FETCH_USER_DATA](context, authCred) {
context.commit(USERS_DATA_REQUEST);
const result = await fetchUsers(authCred).catch(e => {
context.commit(USERS_DATA_ERROR, e);
});
if (result) {
context.commit(USERS_DATA_SUCCESS, result);
}
return result;
}
};
export default {
mutations,
actions,
state: initialState
};
Пояснение к коду:
- Я поместил все мутации, действия и связанные с ними константы в один файл.
- reflectKeys - это метод, который отображает ключи. То есть он возвращает объект со значениемkey и value. Также мы можем добавить префикс к значениям ключей. Он используется только для управления строкой constants для mutation types и action types вместо явной инициализации.
- Vuex предоставляет собственный путь к namespaceдля state, mutations, actions, и т.д. Но я использую другой подход, чтобы не добавлять часть namespace для вызова actions, mutations или state getters и т.д.
- В Vuex мы можем совершать мутации напрямую, не имея действий. Я предлагаю использовать действия вместо выполнения мутаций напрямую, как для синхронных, так и для асинхронных операций. Для этого я экспортировал action types, а не mutation types.
- Действия просто выполняют мутации, Например, всю бизнес-логику. Вызовы API перенесены в services. Нужно поддерживать действия чистыми и хранить бизнес-логику в services. Это делает приложение более гибким благодаря четкому разделению бизнес-логики и управления состоянием.
В корне каждого модуля функций содержится файл состояния этого модуля. Он отвечает за объединение всех состояний данного модуля. Например, в src/app/users/user-state.js. Мы используем модули Vuex для объединения всех состояний этого модуля функций:
import { usersData } from './shared/state';
export default {
modules: {
usersData
}
};
Объединение состояний на уровне объектов с использованием модулей Vuex
Все состояния модулей функций будут объединены в состояние приложения. Это можно сделать, модули Vuex. Например, src/app/app-state.js будет выглядеть следующим образом:
import Vue from 'vue';
import Vuex from 'vuex';
import { usersState } from './users';
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {
usersState
}
});
В этом коде я импортирую usersState из users feature module. Но иногда нужно разделять состояния между разными модулями функций. Этот тип состояния может быть определен в app/shared/state и затем интегрирован в состояние приложения.
Я подготовил шаблон на основе этой архитектуры. Он доступен здесь.
Заключение
Управление состоянием в масштабном приложении является сложной задачей. Поэтому оно должно иметь четко определенную архитектуру.