如何在路由中动态加载组件

Posted

技术标签:

【中文标题】如何在路由中动态加载组件【英文标题】: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
        
    
;

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

【问题讨论】:

【参考方案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)...

【讨论】:

以上是关于如何在路由中动态加载组件的主要内容,如果未能解决你的问题,请参考以下文章

vue路由自动加载、按组件异步载入vuex以及dll优化

vue项目中动态加载路由组件this.$route undefined

如何动态加载路由计算?

Vue路由器将道具传递给动态加载的孩子

Vuejs大应用懒加载动态组件

Angular - 动态加载选项卡名称和组件