https://habr.com/ru/company/otus/blog/506754/- Блог компании OTUS. Онлайн-образование
- JavaScript
- VueJS
Перевод статьи подготовлен в преддверии старта курса «Vue.js-разработчик».
В основе масштабного приложения на Vue.js лежит хранилище, в котором находятся все данные. Хранилище Vuex в приложении на Vue.js служит единым источником истины, который обеспечивает отличную производительность и реактивность из коробки. По мере того, как обрастает кодом ваше приложение, количество данных в хранилище Vuex также растет, и управлять ими становится труднее. Проектирование управления состоянием вашего приложения на основе лучших практик поможет решить большинство проблем, которые появляются по мере усложнения приложения.
В этой статье мы поговорим о некоторых лучших практиках и советах по проектированию архитектуры механизма управления состоянием в масштабном приложении на Vue.js. Мы поговорим о следующих пяти концепциях, которые помогут спроектировать хранилище наилучшим образом.
- Структурирование хранилища
- Разделение хранилища на модули
- Автоматический импорт модулей хранилища
- Сброс состояния модуля
- Глобальный сброс состояния модуля
Структурирование хранилища
Хранилище Vuex состоит из четырех компонентов:
- Объекта состояния
- Функции getter
- Действий
- Мутаций
Если вы еще не знакомы с этими четырьмя концепциями, то сейчас мы вкратце о них поговорим. Объект состояния хранит данные вашего приложения в виде большого JSON-файла. Функция getter помогает вам получить доступ к этим объектам состояния извне, она может вести себя как реактивное вычисляемое свойство. Мутации, как следует из названия, используются для мутации/изменения объекта состояния. Действия очень похожи на мутации, однако, вместо того чтобы изменять состояние, действие коммитят мутации. Действия могут содержать любой асинхронный код или бизнес-логику.
Vuex рекомендует, чтобы объект состояния изменялся только внутри функции-мутации. Также желательно не запускать какой-либо тяжелый или блокирующий код внутри функции-мутации, так как по своей природе он синхронный. Вместо этого нужно использовать действия, которые должны быть спроектированы асинхронно для выполнения тяжелых работ или выполнения сетевых запросов и коммитов мутаций. Действия являются лучшим местом для хранения бизнес-логики и логики обработки данных. Действия считаются лучшими кандидатами на эту роль, потому что вы можете их использовать для отправки данных в хранилище или для отправки данных непосредственно в компоненты Vue.
Хорошей практикой является отказ от прямого доступа к объекту состояния и использование вместо него getter’а. Функцию getter можно легко смаппить в любой компонент Vue с помощью
mapGetter, в качестве вычисляемых свойств.
Разделение хранилища на модули
Неудивительно, что с увеличением размера и сложности хранилище захламляемся и становится более трудным к пониманию. Vuex из коробки предоставляет возможность разделить хранилище на отдельные модули согласно целям в вашем приложении. Разделение бизнес-логики с помощью модульного хранилища повышает удобство сопровождения приложения. Поэтому нам нужно убедиться, что каждый модуль вынесен в отдельное пространство имен, а не обращаться к ним с помощью глобального контекста хранения.
Вот небольшой пример создания модуля хранилища и того, как объединить все модули в основном хранилище.
Структура каталога
store/
├── index.js ---> Main Store file
└── modules/
├── module1.store.js
├── module2.store.js
├── module3.store.js
├── module4.store.js
├── module5.store.js
└── module6.store.js
Обратите внимание, что каждый модуль назван по принципу
ModuleName.store.js
, что поможет в будущем при автоматическом импорте этих модулей, но об этом мы поговорим в следующем разделе.
Авторинг модулей
Мы можем переместить сетевые вызовы в отдельные JavaScript-файлы, но об этом мы поговорим в другой статье, которая будет посвящена архитектуре сетевого уровня приложения. Мы даже можем разделить объекты состояния, getter’ы, действия и мутации на разные файлы для удобства чтения. Хорошо хранить все связанные функций в одном месте и разбивать хранилище на модули все дальше и дальше, если оно все еще слишком большое и сложное.
/* Module1.store.js */
// State object
const state = {
variable1: value,
variable2: value,
variable3: value
}
// Getter functions
const getters = {
getVariable1( state ) {
return state.variable1;
},
getVariable2( state ) {
return state.variable2;
},
....
}
// Actions
const actions = {
fetchVariable1({ commit }) {
return new Promise( (resolve, reject) => {
// Make network request and fetch data
// and commit the data
commit('SET_VARIABLE_1', data);
resolve();
}
},
....
}
// Mutations
const mutations = {
SET_VARIABLE_1(state, data) {
state.variable1 = data;
},
SET_VARIABLE_2(state, data) {
state.variable2 = data;
},
....
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
Комбинирование модулей
/** store/index.js **/
import Vue from 'vue';
import Vuex from 'vuex';
import createLogger from 'vuex/dist/logger';
import Module1 from './modules/module1.store';
import Module2 from './modules/module2.store';
...
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store({
modules: {
Module1,
Module2,
...
},
strict: debug,
plugins: debug? [ createLogger() ] : [],
}
Автоматический импорт модулей хранилища
Как я уже говорил, если модули становятся все сложнее и больше, их нужно разделить на подмодули для уменьшения сложности. Когда количество модулей увеличивается, управлять этими модулями по отдельности становится действительно сложно, а в особенности импортировать каждый из них. У нас будет небольшой JS-файл в каталоге
modules, который сделает эту работу за нас. Он поможет нам объединить вместе все модули.
Чтобы он заработал, рекомендуется следовать строгой структуре именования файлов модулей. В конечном итоге, наличие стандарта именования повысит удобство поддержки всего проекта. Для упрощения работы модули можно назвать в нотации camelCase, и добавить расширение
.store.js
. Например, для файла
userData.store.js
нам нужно будет добавить файл
index.js
внутрь подкаталога модулей, чтобы найти все нужные модули и экспортировать их в основное хранилище.
store/
├── index.js ---> Main Store file
└── modules/
├── index.js --> Auto exporter
├── module1.store.js
└── module2.store.js
Скрипт для автоматического экспорта
/**
* Automatically imports all the modules and exports as a single module object
*/
const requireModule = require.context('.', false, /\.store\.js$/);
const modules = {};
requireModule.keys().forEach(filename => {
// create the module name from fileName
// remove the store.js extension and capitalize
const moduleName = filename
.replace(/(\.\/|\.store\.js)/g, '')
.replace(/^\w/, c => c.toUpperCase())
modules[moduleName] = requireModule(filename).default || requireModule(filename);
});
export default modules;
Теперь, когда наш скрипт для автоматического экспорта находится там, где нужно, мы можем начать импорт в основное хранилище и получить доступ ко всем модулям.
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
// import the auto exporter
import modules from './modules';
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store({
modules, // all your modules automatically imported :)
strict: debug,
plugins: debug ? [createLogger()] : [] // set logger only for development
})
Как только вы использовали автоматический импорт в основном хранилище, все новые модули, добавляемые в подкаталог modules будут автоматически импортированы. Например, если у вас есть файл с именем
user.store.js
, он будет импортирован как модуль хранения в пространстве имен
User. Вы можете использовать это пространство имен для маппинга getter’ов и действий в компоненты с помощью
mapGetters
и
mapActions
.
Сброс состояния модуля
Если вы когда-нибудь работали с приложениями на Vue + Vuex, которые обрабатывают большое количество данных, то, возможно, сталкивались со сценарием, когда нужно было сбросить состояние хранилища. Функция сброса довольно распространенная механика. В случае, когда в вашем приложении есть аутентификация пользователя, вы можете сбросить хранилище при выходе пользователя из приложения.
Чтобы сбросить хранилище, нужно перевести объект состояния в исходное состояние и скопировать его основное состояние. Для этого можно просто использовать функцию, которая возвращает начальное состояние. В модуле хранилища создайте функцию
initialState()
, пусть она возвращает текущий объект состояния.
const initialState = () => ({
variable1: value,
variable2: value,
variable3: value
});
const state = initialState();
Теперь у нас есть отдельное изначальное состояние, и любые изменения, которые мы произведем с состоянием, не повлияют на фактическое изначальное состояние. Таким образом, мы можем использовать полученную функцию для сброса хранилища. Создайте функцию-мутацию, которая с помощью изначального состояния изменит весь объект хранилища.
const initialState = () => ({
variable1: value,
variable2: value,
variable3: value
});
const state = initialState();
// Getters
// Actions
// Mutations
const mutations = {
RESET(state) {
const newState = initialState();
Object.keys(newState).forEach(key => {
state[key] = newState[key]
});
},
// other mutations
}
Как только у нас получилась мутация RESET, мы можем использовать ее для сброса хранилища вызвав действие, либо закоммитив мутацию RESET.
// Actions
const actions = {
reset({ commit }) {
commit('RESET');
},
}
Глобальный сброс состояния модуля
Что делать, если нам нужно сбросить хранилище целиком? Вместе со всеми модулями? Если вы выполнили 4-й и 5-й пункт настройки автоматического импорта и мутации сброса состояния модуля для всех ваших модулей, мы можем использовать следующее действие в файле основного хранилища, чтобы сбросить все модули одновременно.
import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
import modules from './modules';
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export default new Vuex.Store({
modules,
actions: {
reset({commit}) {
// resets state of all the modules
Object.keys(modules).forEach(moduleName => {
commit(`${moduleName}/RESET`);
})
}
},
strict: debug,
plugins: debug ? [createLogger()] : [] // set logger only for development
});
Обратите внимание, что действие, которое мы создали, находится в основном файле хранилища, а не внутри какого-то конкретного модуля. Это действие можно запустить из любого места вашего компонента Vue с помощью следующей строки кода:
this.$store.dispatch('reset');
Что дальше?
Вам понравилась статья? В следующих статьях мы более подробно обсудим аспекты сетевой архитектуры нашего приложения на Vue.js. Мы поговорим о методах, которые используются для управления учетными данными при аутентификации, перехватчиках и обработке ошибок в сетевых запросах.
Чтобы узнать больше о том, чем мы занимаемся в
Locale.ai, прочтите
здесь о неизведанных землях геопространственной аналитики.
Особую благодарность мы выражаем
Крису Фритсу за его удивительный рассказ о
7 вещах, которые скрывают от вас консультанты Vue. Он подкинул нам несколько идей, которые мы использовали в этой статье.
Узнать о курсе подробнее.