Разработка архитектуры управления состоянием в масштабном приложении 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 и затем интегрирован в состояние приложения.

Я подготовил шаблон на основе этой архитектуры. Он доступен здесь.

Заключение

Управление состоянием в масштабном приложении является сложной задачей. Поэтому оно должно иметь четко определенную архитектуру.

Данная публикация представляет собой перевод статьи «Architect state management in a large scale Vue.js application» , подготовленной дружной командой проекта Интернет-технологии.ру

Меню