【问题标题】:How to dynamically load components in routes如何在路由中动态加载组件
【发布时间】:2019-03-05 03:16:42
【问题描述】:

我是 Vue 新手,我正在尝试使用 vue-router 和动态加载组件,而不使用任何额外的库(所以没有 webpack 或类似的库)。

我已经创建了一个索引页面并设置了一个路由器。当我第一次加载页面时,我可以看到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>

ma​​in.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
        }
    }
};

所以问题归结为:如何动态加载组件?

【问题讨论】:

    标签: javascript vue.js vue-component vue-router


    【解决方案1】:

    如何动态加载组件?

    试试这个:

    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

    【讨论】:

    • 感谢您的意见。我来看看。但是,您在文件顶部进行导入,这意味着所有文件将在页面首次加载时加载,而不是在用户转到特定路线时延迟加载。您将如何将延迟加载添加到您的示例中?
    • 哦,我明白了!我已经编辑了我的答案。还有 CodeSandbox 上的项目。
    • 看起来不错。您知道是否有办法在路由定义中进行所有导入?我正计划从 API 加载路由,并且尽可能将所有内容放在一起会很好。
    • 我刚开始使用 VueJS 3 个月,那么我很抱歉我无法帮助这个额外的问题。也许有人知道该怎么做,那么您可能会考虑提出另一个问题。
    • @GTHvidsten 如果确实有帮助,您可能会考虑接受或投票答案:) 你这样做会非常好:)
    【解决方案2】:

    我同意您对“尽可能精简”代码库的愿望,因此在下面制作了这个简单的示例代码(也可通过 https://codesandbox.io/embed/64j8pypr4k 访问)。

    我也不是 Vue 高级用户,但在研究时我想到了三种可能性;

    • 动态imports,
    • requirejs,
    • old school JS 生成 &lt;script src /&gt; 包括。

    看起来最后一个最简单,也最省力:D 可能不是最佳实践,可能很快就会过时(至少在动态导入支持之后)。

    注意:这个例子对更新的浏览器很友好(带有原生 Promises、Fetch、Arrow 函数...)。所以 - 使用最新的 Chrome 或 Firefox 进行测试 :) 支持旧版浏览器可以通过一些 polyfills 和重构等来完成。但这会给代码库增加很多...

    所以 - 按需动态加载组件(之前不包括在内):


    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.log(json);
                that.msg =
                  '<p style="color: green;">' + JSON.stringify(json) + "</p>";
              })
              .catch(error => {
                console.log(error);
                that.msg =
                  '<p style="color: red;">Error fetching: ' + error + "</p>";
              });
          }, 2000);
        }
      },
      created() {
        this.fetchData();
      }
    });
    

    如您所见 - 函数 loadComponent() 在此处执行加载组件的“神奇”事情。

    所以它有效,但就(至少)以下方面而言,它可能不是最好的解决方案:

    • 用JS插入标签可被视为安全问题 在不久的将来,
    • 性能 - 同步加载文件阻塞线程(这可以 成为应用生命后期的主要禁忌),
    • 我没有测试缓存等。在生产中可能是一个真正的问题,
    • 您失去了 (Vue) 组件的美感 - 例如作用域 css、html 和 可以自动捆绑Webpack之类的JS,
    • 你失去了 Babel 编译/转译,
    • 热模块更换(和状态持久性等) - 我相信已经消失了,
    • 我可能忘记了其他明显的问题 高级高级:D

    希望我对你有所帮助:D

    【讨论】:

      【解决方案3】:

      我想看看今天的“新”动态导入有多大用处 (https://developers.google.com/web/updates/2017/11/dynamic-import),所以我用它做了一些实验。它们确实使异步导入更容易,下面是我的示例代码(没有 Webpack / Babel / 只是纯 Chrome 友好的 JS)。

      我将保留我的旧答案 (How to dynamically load components in routes) 以供参考 - 与动态导入 (https://caniuse.com/#feat=es6-module-dynamic-import) 相比,以这种方式加载脚本适用于更多浏览器。

      所以最后我注意到你实际上非常、非常、非常接近你的工作——这实际上只是导出导入的 JS 模块时的语法错误(缺少逗号)。

      下面的示例也对我有用(不幸的是 Codesandbox 的 (es)lint 不允许使用该语法,但我已经在本地检查过它并且它有效(在 Chrome 中,即使 Firefox 还不喜欢该语法:(SyntaxError: the 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>
      

      ma​​in.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.log("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: '&lt;div&gt;SubPage path: {{msg}}&lt;/div&gt;' 之后缺少了一个逗号。

      不过,这似乎只适用于:

      • Chrome >= v63
      • Android 版 Chrome >= v69
      • Safari >= v11.1
      • IOS Safari >= v11.2

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-22
        • 2018-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-22
        • 1970-01-01
        • 2017-12-06
        相关资源
        最近更新 更多