Как получить доступ к асинхронному хранилищу данных в 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')

})
Другие вопросы по тегам