如何在路由中动态加载组件
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>
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
;
所以问题归结为:如何动态加载组件?
【问题讨论】:
【参考方案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 高级用户,但在研究时我想到了三种可能性;
动态import
s,
require
js,
old school JS 生成 <script src />
包括。
看起来最后一个最简单,也最省力: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>
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.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: '<div>SubPage path: msg</div>'
之后缺少了一个逗号。
不过,这似乎只适用于:
Chrome >= v63 android 版 Chrome >= v69 Safari >= v11.1 ios Safari >= v11.2(https://caniuse.com/#feat=es6-module-dynamic-import)...
【讨论】:
以上是关于如何在路由中动态加载组件的主要内容,如果未能解决你的问题,请参考以下文章