Как отобразить анимацию "загрузки" при загрузке компонента с ленивым загрузчиком?

Я разделил свое приложение на несколько фрагментов с функцией разделения кода веб-пакета, чтобы весь пакет приложений не загружался, когда пользователь посещает мою веб-страницу.

Куски, которые требуются для некоторых маршрутов, могут быть достаточно большими и могут занимать значительное количество времени для загрузки. Это прекрасно, за исключением того, что пользователь не знает, что страница загружается, когда они нажимают на внутреннюю ссылку, поэтому мне нужно каким-то образом отобразить анимацию загрузки или что-то в этом роде.

Мой маршрутизатор настроен следующим образом:

[
  {
    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 в руководстве Vue.js показывает, как отображать конкретный компонент загрузки при разрешении компонента - это это то, что мне нужно, однако оно также говорит:

Обратите внимание, что при использовании в качестве компонента маршрута в vue-router эти свойства будут игнорироваться, поскольку компоненты асинхронного доступа будут устранены до начала навигации по маршруту.

Как я могу добиться этого в vue-router? Если это невозможно, лениво загруженные компоненты будут для меня бесполезны, потому что это обеспечит пользователю плохой опыт.

Ответ 1

Вы можете использовать защитные устройства для активации/деактивации состояния загрузки, которое показывает/скрывает загружаемый компонент:

Если вы хотите использовать что-то вроде "nprogress", вы можете сделать это следующим образом:

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

const router = new VueRouter({
  routes
})

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

В качестве альтернативы, если вы хотите показать свое место на месте:

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()
})

Затем в шаблоне:

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

Это также можно легко инкассировать в очень маленьком компоненте вместо использования $root-компонента для состояния загрузки.

Ответ 2

Что бы это ни стоило, я поделюсь тем, что я в итоге сделал для моей ситуации.

Я использую Vuex, поэтому было легко создать состояние "загрузки" для всего приложения, к которому может обращаться любой компонент, но вы можете использовать любой механизм, которым хотите поделиться этим состоянием.

Упрощенно, это работает так:

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')),
    },
  ];
}

Поэтому все, что мне нужно сделать, это обернуть every () => import() моей функцией load() которая заботится о настройке состояния загрузки. Загрузка определяется путем непосредственного наблюдения за обещанием, а не для того, чтобы зависеть от конкретного маршрутизатора до/после хуков.