Как динамически загружать компоненты в маршруты

Я новичок в Vue, и я экспериментирую с vue-router и динамической загрузкой компонентов без использования каких-либо дополнительных библиотек (так что никаких веб-пакетов или подобных).

Я создал индексную страницу и настроил маршрутизатор. Когда я впервые загружаю страницу, я вижу, что subpage.js не загружен, а когда я щелкаю <router-link>, я вижу, что файл subpage.js загружен. Однако URL-адрес не меняется, и компонент не отображается.

Вот что у меня есть на данный момент:

index.html

<html>
<head>
    <script src = "https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src = "https://unpkg.com/vue-router/dist/vue-router.js"></script>
</head>
<body>
    <div id = "app">
      <h1>Hello App!</h1>
      <router-link to = "/subpage">To subpage</router-link>
      <router-view></router-view>
    </div>
    <script src = "main.js"></script>
</body>
</html>

main.js

const router = new VueRouter({
  routes: [
    { path: '/subpage', component: () => import('./subpage.js') }
  ]
})

const app = new Vue({
    router
}).$mount('#app');

subpage.js

export default {
    name: 'SubPage',
    template: '<div>SubPage path: {{msg}}</div>'
    data: function() {
        return {
            msg: this.$route.path
        }
    }
};

Итак, вопрос сводится к следующему: как я могу динамически загружать компонент?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
0
3 264
3

Ответы 3

How can I dynamically load a component?

Попробуй это:

App.vue

<template>
  <div id = "app">
    <router-link to = "/">Home</router-link>
    <router-link to = "/about">About</router-link>
    <hr/>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'app',
  components: {}
};
</script>

main.js

import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';

Vue.use(VueRouter);
Vue.config.productionTip = false;

const Home = () => import('./components/Home.vue');
const About = () => import('./components/About.vue');

const router = new VueRouter({
  mode: 'history',
  routes:[
    {path:'/', component: Home},
    {path:'/about',component: About}
  ]
})
new Vue({
  router,
  render: h => h(App)
}).$mount('#app');

Home.vue

<template>
  <div>
    <h2>Home</h2>
  </div>
</template>

<script>
export default {
  name: 'Home'
};
</script>

About.vue

<template>
  <div>
    <h2>About</h2>
  </div>
</template>

<script>
export default {
  name: 'About'
};
</script>

Таким образом, компонент Home будет загружен автоматически.

Это демонстрация: https://codesandbox.io/s/48qw3x8mvx

Спасибо за вклад. Я посмотрю. Однако вы выполняете импорт в верхней части файла, что означает, что все файлы будут загружены при первой загрузке страницы, вместо того, чтобы загружаться лениво, когда пользователь переходит по определенному маршруту. Как бы вы добавили в свой пример отложенную загрузку?

GTHvidsten 29.09.2018 11:51

О, теперь я понял! Я отредактировал свой ответ. А также проект на CodeSandbox.

You Nguyen 29.09.2018 11:54

Хорошо выглядеть. Вы знаете, есть ли способ выполнить весь импорт в определениях маршрутов? Я планирую загружать маршруты из API, и было бы неплохо сделать все как можно ближе друг к другу.

GTHvidsten 29.09.2018 12:06

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

You Nguyen 29.09.2018 12:13

@GTHvidsten Вы можете подумать о том, чтобы принять или проголосовать за ответ, если он действительно поможет :) Было бы очень приятно, если бы вы это сделали :)

You Nguyen 29.09.2018 13:21

Я сделаю это, но я всегда придерживаюсь политики ожидания, по крайней мере, несколько дней, чтобы принять решение, чтобы дать другим возможность ответить;)

GTHvidsten 29.09.2018 19:37

Я наконец нашел время, чтобы проверить это, и я сожалею, что это не сработало. Я изменил свой main.js, чтобы загрузить подстраницу перед определением маршрутизатора: const subpage = () => import('./subpage.js'); и использовать эту переменную в определении маршрута вместо прямого импорта: { path: '/subpage', component: subpage }. Subpage.js загружается динамически, но содержимое компонента и URL-адрес по-прежнему не отображается / не изменяется.

GTHvidsten 29.09.2018 20:37

Я также заметил, что ваш codeandbox-example использует webpack + babel + различные другие фреймворки, тогда как я ищу решение без "любые дополнительные библиотеки (т. е. никаких веб-пакетов и т.п.)".

GTHvidsten 29.09.2018 20:59

Я разделяю ваши пожелания о «максимально экономичной» кодовой базе и поэтому сделал этот простой пример кода ниже (также доступный по адресу https://codesandbox.io/embed/64j8pypr4k).

Я тоже не являюсь опытным пользователем Vue, но при исследовании я подумал о трех возможностях;

  • динамические imports,
  • requirejs,
  • олдскульный JS сгенерировал <script src /> include.

Похоже, последний самый простой и требует меньше всего усилий: D Вероятно, это не лучшая практика и, вероятно, скоро устареет (по крайней мере, с поддержкой динамического импорта).

NB: этот пример удобен для более поздних браузеров (с встроенными функциями Promises, Fetch, Arrow ...). Итак - используйте последнюю версию Chrome или Firefox для тестирования :) Поддержка старых браузеров может быть реализована с помощью некоторых полифилов, рефакторинга и т. д. Но это значительно добавит кодовую базу ...

Итак - динамически загружаемые компоненты по запросу (и не включенные ранее):


index.html

<html>

<head>
    <meta charset = "utf-8">
    <meta http-equiv = "x-ua-compatible" content = "ie=edge">
    <meta name = "viewport" content = "width=device-width, initial-scale=1">
    <title>Vue lazyload test</title>
  <style>
html,body{
  margin:5px;
  padding:0;
  font-family: sans-serif;
}

nav a{
  display:block;
  margin: 5px 0;
}

nav, main{
  border:1px solid;
  padding: 10px;
  margin-top:5px;
}

    .output {
        font-weight: bold;
    }

  </style>
</head>

<body>
    <div id = "app">
    <nav>
      <router-link to = "/">Home</router-link>
      <router-link to = "/simple">Simple component</router-link>
      <router-link to = "/complex">Not sooo simple component</router-link>
    </nav>
      <main>
          <router-view></router-view>
    </main>
    </div>

<script src = "https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.0.1/vue-router.min.js"></script>
<script>
    function loadComponent(componentName, path) {
      return new Promise(function(resolve, reject) {
        var script = document.createElement('script');

        script.src = path;
        script.async = true;

        script.onload = function() {
          var component = Vue.component(componentName);

          if (component) {
            resolve(component);
          } else {
            reject();
          }
        };
        script.onerror = reject;

        document.body.appendChild(script);
      });
    }

    var router = new VueRouter({
      mode: 'history',
      routes: [
        {
          path: '/',
          component: {
            template: '<div>Home page</div>'
          },
        },
        {
          path: '/simple',
          component: function(resolve, reject) {
            loadComponent('simple', 'simple.js').then(resolve, reject);
          }
        },
         { path: '/complex', component: function(resolve, reject) { loadComponent('complex', 'complex.js').then(resolve, reject); }
        }
      ]
    });

    var app = new Vue({
      el: '#app',
      router: router,
    });
</script>

</body>

</html>

simple.js:

Vue.component("simple", {
  template: "<div>Simple template page loaded from external file</div>"
});

complex.js:

Vue.component("complex", {
  template:
    "<div class='complex-content'>Complex template page loaded from external file<br /><br />SubPage path: <i>{{path}}</i><hr /><b>Externally loaded data with some delay:</b><br /> <span class='output' v-html='msg'></span></div>",
  data: function() {
    return {
      path: this.$route.path,
      msg: '<p style = "color: yellow;">Please wait...</p>'
    };
  },
  methods: {
    fetchData() {
      var that = this;
      setTimeout(() => {
        /* a bit delay to simulate latency :D */
        fetch("https://jsonplaceholder.typicode.com/todos/1")
          .then(response => response.json())
          .then(json => {
            console.info(json);
            that.msg =
              '<p style = "color: green;">' + JSON.stringify(json) + "</p>";
          })
          .catch(error => {
            console.info(error);
            that.msg =
              '<p style = "color: red;">Error fetching: ' + error + "</p>";
          });
      }, 2000);
    }
  },
  created() {
    this.fetchData();
  }
});

Как видите - здесь функция loadComponent() творит "волшебство" загрузки компонентов.

Итак, это работает, но, вероятно, это не лучшее решение в отношении (по крайней мере) следующего:

  • вставка тегов с помощью JS может рассматриваться как проблема безопасности в ближайшем будущем,
  • производительность - синхронная загрузка файлов блокирует поток (это может быть главным запретом в дальнейшей жизни приложения),
  • Я не тестировал кеширование и т. д. Может быть реальной проблемой в продакшене,
  • Вы теряете красоту компонентов (Vue), таких как CSS, html и JS, который можно автоматически связать с Webpack или чем-то еще,
  • Вы теряете компиляцию / транспиляцию Babel,
  • Горячая замена модуля (и сохранение состояния и т. д.) - исчезла, я считаю,
  • Я, наверное, забыл о других проблемах, которые очевидны для старшеклассники: D

Надеюсь, я вам помог: D

Я хотел увидеть, насколько сегодня полезен «новый» динамический импорт (https://developers.google.com/web/updates/2017/11/dynamic-import), поэтому я провел с ним несколько экспериментов. Они делают асинхронный импорт намного проще, и ниже мой пример кода (без Webpack / Babel / просто чистый Chrome-friendly JS).

Я сохраню свой старый ответ (Как динамически загружать компоненты в маршруты) для потенциальной справки - загрузка скриптов таким образом работает в большем количестве браузеров, чем динамический импорт (https://caniuse.com/#feat=es6-module-dynamic-import).

Итак, в конце я заметил, что вы на самом деле были очень, очень, очень близки к своей работе - на самом деле это была просто синтаксическая ошибка при экспорте импортированного модуля JS (без запятой).

Пример, приведенный ниже, также работал у меня (к сожалению, lint Codesandbox (es) не разрешает синтаксис, но я проверил его локально, и он работал (в Chrome даже Firefox еще не нравится синтаксис: (SyntaxError: ключевое слово import может только появятся в модуле)));


index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset = "utf-8" /> 
  <title>Page Title</title>
</head>

<body>
    <div id = "app">
      <h1>Hello App!</h1>
      <router-link to = "/temp">To temp</router-link>
      <router-link to = "/module">To module</router-link>
      <router-view></router-view>
    </div>
    <script src = "https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src = "https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <script src = "main.js"></script>
</body>
</html>

main.js:

'use strict';

const LazyRouteComponent = {
    template: '<div>Route:{{msg}}</div>',
    data: function() {
        return {
            msg: this.$route.path
        }
    }
}


const router = new VueRouter({
  routes: [
    {
        path: '/temp',
        component: {
            template: '<div>Hello temp: {{msg}}</div>',
            data: function() {
                return {
                    msg: this.$route.path
                }
            }
        }
    },
    { path: '/module', name: 'module', component:  () => import('./module.js')},
    { path: '*', component: LazyRouteComponent }
  ]
})

const app = new Vue({
    router
}).$mount('#app');

и ключевое отличие, module.js:

export default {
    name: 'module',
    template: '<div>Test Module loaded ASYNC this.$route.path:{{msg}}</div>',
    data: function () {
       return {
          msg: this.$route.path
       }
    },
    mounted: function () {
      this.$nextTick(function () {
        console.info("entire view has been rendered after module loaded Async");
      })
    }
}

Итак - все в точности как ваш код - но со всеми запятыми;

subpage.js

export default {
    name: 'SubPage',
    template: '<div>SubPage path: {{msg}}</div>',
    data: function() {
        return {
            msg: this.$route.path
        }
    }
};

Итак - ваш код работает (я тестировал его копированием) - вам просто не хватало запятой после template: '<div>SubPage path: {{msg}}</div>'.

Тем не менее, это работает только в:

  • Chrome> = v63
  • Chrome для Android> = v69
  • Safari> = v11.1
  • IOS Safari> = v11.2

(https://caniuse.com/#feat=es6-module-dynamic-import) ...

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