Как получить доступ к асинхронному хранилищу данных в vue-router для использования в beforeEnter hook?
Как можно получить доступ к данным хранилища в beforeEnter, который извлекается асинхронно с помощью действия хранилища?
import store from './vuex/store';
store.dispatch('initApp'); // in here, async data will be fetched and assigned to the store's state
// following is an excerpt of the routes object:
{
path: '/example',
component: Example,
beforeEnter: (to, from, next) =>
{
if (store.state.asyncData) {
// the above state is not available here, since it
// it is resolved asynchronously in the store action
}
}
}
Это особенно важно при загрузке первой страницы или после перезагрузки страницы, когда извлекаются данные инициализации, и маршрутизатору нужно ждать, пока эти данные позволят пользователю получить доступ к этой странице или нет.
Возможно ли, чтобы маршрутизатор "ждал" данных, которые будут выбраны? Или как лучше всего управлять навигационной защитой в сочетании с асинхронными данными хранилища vuex?
(о, и предварительное заполнение "asyncData" не может быть решением, поскольку ловушка beforeEnter должна принять решение о реальных данных из базы данных, а не данных по умолчанию)
5 ответов
Вы можете сделать это, вернув обещание от действия vuex, как это объясняется здесь, и вызвать отправку изнутри beforeEnter
сам.
Код должен выглядеть следующим образом:
import store from './vuex/store';
// following is an excerpt of the routes object:
{
path: '/example',
component: Example,
beforeEnter: (to, from, next) =>
{
store.dispatch('initApp').then(response => {
// the above state is not available here, since it
// it is resolved asynchronously in the store action
}, error => {
// handle error here
})
}
}
Вам действительно нужно асинхронно получать данные с сервера перед каждым изменением маршрута, или вам просто нужно сохранить некоторые данные из хранилища, чтобы они не "исчезали" при перезагрузке страницы или когда пользователь использует прямую ссылку?
В последнем случае вы можете сохранить данные аутентификации (например, токен JWT) в localStorage/cookie, а затем просто извлечь их из хранилища во время инициализации (и зафиксировать их для хранения) - это должно быть синхронным действием.
Вы также можете сохранить все состояние хранилища, используя vuex-persistedstate, чтобы он не исчезал при перезагрузке и не нуждался в гидратации.
Если вам нужно извлечь некоторые данные асинхронно один раз перед первой загрузкой или перезагрузкой страницы, вы можете отправить действие хранилища и инициализировать экземпляр Vue в then()
обратный вызов - но это может зависеть от вашей реализации. Как то так (в main.js
):
import Vue from 'vue';
import VueRouter from 'vue-router';
import { sync } from 'vuex-router-sync';
// contains VueRouter instance and route definitions
import router from 'src/router/router';
// contains Vuex.Store instance. I guess this is './vuex/store' in your example?
import store from 'src/store/store';
// sync the router with vuex store using vuex-router-sync
Vue.use(VueRouter);
sync(store, router);
// dispatch init action, doing asynchronous stuff, commiting to store
store.dispatch('initApp').then(() => {
// create the main Vue instance and mount it
new Vue({
router,
store
}).$mount('#app');
});
Я решил эту проблему, используя store.watch() без начального значения и возвращая последнее значение, когда оно уже инициализировано.
Вот мой пример кода
async function redirectIfNotAuth (to, from, next) {
const user = await getUserState()
if (user === null) {
next({ name: 'auth' })
} else {
next()
}
}
function getUserState () {
return new Promise((resolve, reject) => {
if (store.state.user === undefined) {
const unwatch = store.watch(
() => store.state.user,
(value) => {
unwatch()
resolve(value)
}
)
} else {
resolve(store.state.user)
}
})
}
/* STORE */
const store = new Vuex.Store({
state: {
user: undefined
}
})
/* ROUTER */
new Router({
routes: [
{
path: '',
name: 'home',
component: Home,
beforeEnter: redirectIfNotAuth
},
{
path: '/signin',
name: 'auth',
component: Auth,
}
]
})
Самый простой способ — использовать await/async, учитывая вашinitApp
действие такжеasync
или возвращает обещание
import store from './vuex/store';
// following is an excerpt of the routes object:
{
path: '/example',
component: Example,
async beforeEnter(to, from, next) {
if (!store.state.isInited) {
await store.dispatch('initApp');
}
next();
}
}
Это мое решение:
Сначала определите асинхронную функцию для загрузки удаленных данных.
import {Common_Config} from "@/app/config/Common";
export class Life_Boot {
public static async prepareRemoteLoading(params : any = {}) {
return new Promise(async (resolve, reject) => {
let res = await this.initialize(params)
resolve(res)
})
}
protected static async initialize(params : any = {}) {
const client = Common_Config.init(params)
// load remote config, get user roles and menus, add routes dynamically
await client.context('life').waitInitialized()
return client
}
}
Во-вторых, создайте экземпляр Vue в функции then().
// at the bottom of the main.js
Life_Boot.prepareRemoteLoading().then(client => {
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app')
})