web-dev-qa-db-fra.com

Comment afficher une animation de "chargement" pendant le chargement d'un composant d'itinéraire paresseux?

J'ai divisé mon application en plusieurs morceaux avec la fonction de fractionnement de code de webpack afin que l'ensemble de l'application ne soit pas téléchargé lorsque l'utilisateur visite ma page Web.

Les segments requis par certains itinéraires peuvent être assez volumineux et leur téléchargement peut prendre un certain temps. C'est très bien, sauf que l'utilisateur ne sait pas que la page se charge réellement lorsqu'il clique sur un lien interne, donc je dois afficher une animation de chargement ou quelque chose.

Mon routeur est configuré comme ceci:

[
  {
    path: '/',
    component: () => import(/* webpackChunkName: 'landing' */ './landing.vue'),
  },
  {
    path: '/foo',
    component: () => import(/* webpackChunkName: 'main' */ './foo.vue'),
  },
  {
    path: '/bar',
    component: () => import(/* webpackChunkName: 'main' */ './bar.vue'),
  },
]

Advanced Async Components dans le guide Vue.js montre comment afficher un composant de "chargement" particulier pendant la résolution du composant - c'est exactement ce dont j'ai besoin, mais il dit aussi:

Notez que lorsqu'ils sont utilisés en tant que composant d'itinéraire dans vue-router, ces propriétés seront ignorées car les composants asynchrones sont résolus d'avance avant que la navigation d'itinéraire ne se produise.

Comment puis-je y parvenir dans vue-router? Si ce n'est pas possible, les composants chargés paresseusement me seraient à peu près inutiles car cela fournirait une mauvaise expérience à l'utilisateur.

19
Decade Moon

Vous pouvez utiliser des gardes de navigation pour activer/désactiver un état de chargement qui affiche/masque un composant de chargement:

Si vous souhaitez utiliser quelque chose comme "nprogress", vous pouvez le faire comme ceci:

http://jsfiddle.net/xgrjzsup/2669/

const router = new VueRouter({
  routes
})

router.beforeEach((to, from, next) => {
  NProgress.start()
  next()
})
router.afterEach(() => {
  NProgress.done()
})

Alternativement, si vous voulez montrer quelque chose sur place:

http://jsfiddle.net/h4x8ebye/1/

Vue.component('loading',{ template: '<div>Loading!</div>'})

const router = new VueRouter({
  routes
})

const app = new Vue({
  data: { loading: false },
  router
}).$mount('#app')

router.beforeEach((to, from, next) => {
  app.loading = true
    next()
})

router.afterEach((to, from, next) => {
  setTimeout(() => app.loading = false, 1500) // timeout for demo purposes
    next()
})

Puis dans le modèle:

<loading v-if="$root.loading"></loading>
  <router-view v-else></router-view>

Cela pourrait également être facilement encapsulé dans un très petit composant au lieu d'utiliser le composant $ root pour l'état de chargement.

32
Linus Borg

Pour ce que ça vaut, je vais partager ce que j'ai fini par faire pour ma situation.

J'utilise Vuex, il était donc facile de créer un état de "chargement" à l'échelle de l'application auquel n'importe quel composant peut accéder, mais vous pouvez utiliser le mécanisme que vous souhaitez partager cet état.

Simplifié, cela fonctionne comme ceci:

function componentLoader(store, fn) {
  return () => {
    // (Vuex) Loading begins now
    store.commit('LOADING_BAR_TASK_BEGIN');

    // (Vuex) Call when loading is done
    const done = () => store.commit('LOADING_BAR_TASK_END');

    const promise = fn();
    promise.then(done, done);
    return promise;
  };
}

function createRoutes(store) {
  const load = fn => componentLoader(store, fn);

  return [
    {
      path: '/foo',
      component: load(() => import('./components/foo.vue')),
    },
    {
      path: '/bar',
      component: load(() => import('./components/bar.vue')),
    },
  ];
}

Donc, tout ce que j'ai à faire est d'envelopper chaque () => import() Par ma fonction load() qui s'occupe de définir l'état de chargement. Le chargement est déterminé en observant directement la promesse au lieu de s'appuyer sur des crochets avant/après spécifiques au routeur.

2
Decade Moon