Как разделить store на модули в Vuex?
Vuex позволяет разделять хранилище на модули, чтобы сделать структуру приложения более масштабируемой и читаемой. Каждый модуль может содержать собственные state, getters, mutations и actions, и может быть как вложенным, так и динамически регистрируемым.
Зачем делить Vuex на модули
Когда приложение растёт, центральное хранилище становится перегруженным. Чтобы не хранить всю логику в одном файле, Vuex предоставляет возможность разбиения store на модули. Это позволяет:
-
изолировать логику по функциональным зонам (auth, user, cart, products и т.д.);
-
повторно использовать структуру и логику;
-
упростить тестирование и сопровождение;
-
применять пространственную изоляцию (namespacing).
Базовая структура модулей
Обычно структура проекта с модулями в Vuex выглядит так:
src/
├── store/
│ ├── index.js
│ ├── modules/
│ │ ├── auth.js
│ │ ├── cart.js
│ │ └── products.js
Пример модуля (auth.js)
// store/modules/auth.js
export default {
namespaced: true,
state() {
return {
user: null,
token: null
};
},
mutations: {
setUser(state, user) {
state.user = user;
},
setToken(state, token) {
state.token = token;
}
},
actions: {
login({ commit }, credentials) {
// имитация запроса
const token = 'fake-token';
const user = { id: 1, name: 'Admin' };
commit('setUser', user);
commit('setToken', token);
}
},
getters: {
isAuthenticated(state) {
return !!state.token;
}
}
};
Регистрируем модули в store (index.js)
// store/index.js
import { createStore } from 'vuex';
import auth from './modules/auth';
import cart from './modules/cart';
import products from './modules/products';
const store = createStore({
modules: {
auth,
cart,
products
}
});
export default store;
Пространства имён (namespaced: true)
По умолчанию все модули работают в общем пространстве имён. Установка namespaced: true в модуле изолирует его мутации, геттеры и экшены.
Для доступа к ним нужно использовать полные пути:
store.dispatch('auth/login', credentials);
store.commit('cart/addItem', item);
store.getters\['products/availableProducts'\];
Использование в компонентах
import { useStore } from 'vuex';
import { computed } from 'vue';
setup() {
const store = useStore();
const user = computed(() => store.state.auth.user);
const isAuthenticated = computed(() => store.getters\['auth/isAuthenticated'\]);
const login = () => {
store.dispatch('auth/login', { username: 'admin', password: '1234' });
};
return { user, isAuthenticated, login };
}
Локальные mapState, mapActions, mapGetters (в Options API)
Vuex предоставляет утилиты mapState, mapGetters, mapMutations, mapActions:
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('auth', \['user', 'token'\])
},
methods: {
...mapActions('auth', \['login'\])
}
};
Вложенные модули
Модули Vuex могут быть вложенными. Пример:
const moduleA = {
namespaced: true,
modules: {
subModule: {
namespaced: true,
state() {
return { value: 42 };
},
getters: {
getValue: state => state.value
}
}
}
};
Обращение к геттеру:
store.getters\['moduleA/subModule/getValue'\];
Динамическая регистрация модулей
Vuex позволяет регистрировать модули на лету (например, при входе в систему или открытии раздела):
store.registerModule('settings', {
state: { theme: 'dark' },
mutations: {
setTheme(state, theme) {
state.theme = theme;
}
}
});
Удаление модуля:
store.unregisterModule('settings');
Также можно проверять наличие модуля перед регистрацией, используя store.hasModule.
Модули без namespaced
Если модуль не использует namespaced: true, его state, getters, actions, mutations будут объединены с глобальным пространством, и к ним можно обращаться напрямую:
store.commit('setUser', user); // если setUser из auth без namespace
Это допустимо для очень маленьких приложений, но при масштабировании лучше использовать изоляцию с namespace.
Особенности типизации (TypeScript)
При использовании TypeScript стоит быть осторожным с структурой модулей и типами. В случае с модулями с namespaced: true требуется вручную прописывать типы при работе с store, или использовать дополнительные обёртки (например, createNamespacedHelpers). Также библиотека typed-vuex упрощает работу с типизированными модулями.
Пример нескольких модулей
// store/modules/cart.js
export default {
namespaced: true,
state() {
return {
items: \[\]
};
},
mutations: {
addItem(state, item) {
state.items.push(item);
}
},
getters: {
itemCount: state => state.items.length
}
};
// store/modules/products.js
export default {
namespaced: true,
state() {
return {
all: \[\]
};
},
actions: {
fetchProducts({ commit }) {
// имитация API-запроса
const data = \[{ id: 1, name: 'Product A' }\];
commit('setProducts', data);
}
},
mutations: {
setProducts(state, products) {
state.all = products;
}
}
};
// store/index.js
import cart from './modules/cart';
import products from './modules/products';
export default createStore({
modules: {
cart,
products
}
});