Доступ к router в store

В архитектуре Vue.js с использованием Vue Router и Vuex часто возникает необходимость управлять маршрутизацией прямо из состояния приложения, т.е. инициировать переходы по маршрутам из действий или мутаций Vuex. Прямой доступ к объекту router в store не предусмотрен по умолчанию, поэтому требуется корректная интеграция для сохранения чистоты архитектуры и реактивности приложения.


Передача экземпляра router в store

Самый распространённый подход — передача экземпляра router при инициализации store. Это позволяет действиям Vuex вызывать маршруты без прямого импорта модуля router, что сохраняет тестируемость и модульность кода.

Пример передачи router:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  { path: '/', component: Home },
  { path: '/about', component: About }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

// store/index.js
import { createStore } from 'vuex';

export default function createMyStore(router) {
  return createStore({
    state: {
      userLoggedIn: false
    },
    mutations: {
      setLoginState(state, value) {
        state.userLoggedIn = value;
      }
    },
    actions: {
      login({ commit }, { router }) {
        commit('setLoginState', true);
        router.push('/about');
      }
    }
  });
}

При создании store необходимо передать объект router:

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import createMyStore from './store';

const store = createMyStore(router);

createApp(App)
  .use(store)
  .use(router)
  .mount('#app');

Ключевой момент: передача router при инициализации делает его доступным в действиях store без глобальных импортов, что облегчает тестирование и модульность.


Использование rootState и rootGetters для маршрутизации

Если store модульный, доступ к router через отдельный модуль может быть организован через rootState или rootGetters. Например, можно хранить текущий маршрут в состоянии и реагировать на его изменения.

const navigationModule = {
  namespaced: true,
  state: {
    currentRoute: '/'
  },
  mutations: {
    setRoute(state, route) {
      state.currentRoute = route;
    }
  },
  actions: {
    navigate({ commit, rootState }, { path, router }) {
      commit('setRoute', path);
      router.push(path);
    }
  }
};

Такой подход позволяет отслеживать историю переходов в store и синхронизировать состояние интерфейса с маршрутизацией.


Доступ к router через глобальный Vue объект

В Vue 3 можно использовать provide/inject для передачи экземпляра router в store. Например:

// main.js
import { createApp, provide } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';

const app = createApp(App);

app.provide('router', router);
app.use(store);
app.use(router);
app.mount('#app');

// store/actions.js
export function login({ commit }, { router }) {
  commit('setLoginState', true);
  router.push('/dashboard');
}

Этот метод особенно полезен, если store создается централизованно, а доступ к router требуется лишь в отдельных модулях.


Асинхронная маршрутизация из действий Vuex

Vuex позволяет выполнять асинхронные действия перед изменением маршрута. Это актуально для проверки прав пользователя или загрузки данных перед переходом.

Пример:

actions: {
  async secureNavigate({ commit }, { router, path, checkAuth }) {
    const isAuthorized = await checkAuth();
    if (isAuthorized) {
      router.push(path);
      commit('setRoute', path);
    } else {
      router.push('/login');
    }
  }
}

Такой подход позволяет обрабатывать сложную логику перед сменой маршрута без нарушения реактивности компонентов.


Интеграция с TypeScript

При использовании TypeScript важно корректно типизировать router в store:

import { Router } from 'vue-router';

interface ActionContextWithRouter {
  commit: Function;
  state: any;
  router: Router;
}

const actions = {
  login({ commit, router }: ActionContextWithRouter) {
    commit('setLoginState', true);
    router.push('/dashboard');
  }
};

Типизация помогает избежать ошибок при работе с методами router.push и router.replace и обеспечивает автодополнение в редакторах.


Лучшие практики

  • Не хранить сам объект router в состоянии Vuex (state), чтобы не нарушать реактивность.
  • Передавать router только в действия через аргументы или при создании store.
  • Для модульного store использовать namespaced-модули и rootState для синхронизации маршрутов.
  • Асинхронные проверки перед переходом на новый маршрут должны выполняться в actions, а не в мутациях.
  • В TypeScript обязательно использовать корректные типы для router и payload, чтобы избежать runtime ошибок.

Эти подходы позволяют организовать чистую архитектуру взаимодействия Vuex и Vue Router, сохраняя модульность, реактивность и тестируемость приложения.