单页应用程序有时会因为初始加载缓慢而出现一些问题。这是因为传统上,服务器会向客户端发送大量 JavaScript,必须在屏幕上显示任何内容之前下载和解析这些 JavaScript。正如您可以想象的那样,随着您的应用程序规模的增长,这可能会变得越来越成问题。
幸运的是,在使用 Vue CLI(在后台使用 webpack)构建 Vue 应用程序时,可以采取多种措施来抵消这种情况。在本文中,我将演示如何利用异步组件和 webpack 的代码拆分功能在应用程序初始呈现后加载页面的某些部分。这将使初始加载时间保持在最低限度,并为您的应用程序带来更快速的感觉。
要学习本教程,您需要对 Vue.js 和可选的 Node.js 有基本的了解。
异步组件 {#asynccomponents}
在我们深入创建异步组件之前,让我们先看看我们通常是如何加载一个组件的。为此,我们将使用一个非常简单的消息组件:
<!-- Message.vue -->
<template>
<h1>New message!</h1>
</template>
现在我们已经创建了我们的组件,让我们将它加载到我们的App.vue
文件中并显示它。我们可以只导入组件并将其添加到组件选项中,以便我们可以在我们的模板中使用它:
<!-- App.vue -->
<template>
<div>
<message></message>
</div>
</template>
<script>
import Message from "./Message";
export default {
components: {
Message
}
};
</script>
但是现在发生了什么?Message每当加载应用程序时都会加载该组件,因此它包含在初始加载中。
对于一个简单的应用程序来说,这听起来可能不是一个大问题,但考虑一些更复杂的东西,比如网上商店。想象一下,用户将商品添加到购物篮中,然后想要结账,因此单击结账按钮,该按钮会呈现一个包含所选商品所有详细信息的框。使用上述方法,这个结帐框将包含在初始包中,尽管我们只在用户单击结帐按钮时才需要该组件。用户甚至可能在没有点击结帐按钮的情况下浏览网站,这意味着将资源浪费在加载这个可能未使用的组件上是没有意义的。
为了提高应用程序的效率,我们可以将延迟加载和代码拆分技术结合起来。
延迟加载就是延迟组件的初始加载。您可以在 medium.com 等网站上看到延迟加载的实际应用,其中图像在需要之前加载。这很有用,因为我们不必浪费资源预先加载特定帖子的所有图像,因为读者可能会中途跳过文章。
webpack 提供的代码拆分功能允许您将代码拆分为多个包,然后可以按需加载或在以后的某个时间点并行加载。它可用于仅在需要或使用时加载特定的代码片段。
动态导入
幸运的是,Vue 使用称为动态导入的东西来迎合这种情况。此功能引入了一种新的类似函数的导入形式,它将返回包含请求的 (Vue) 组件的 Promise。由于 import 是一个接收字符串的函数,我们可以做一些强大的事情,比如使用表达式加载模块。从版本 61 开始,动态导入在 Chrome 中可用。有关它们的更多信息,请访问 Google Developers 网站。
代码拆分由 webpack、Rollup 或 Parcel 等打包器负责,它们了解动态导入语法并为每个动态导入的模块创建一个单独的文件。我们稍后会在控制台的网络选项卡中看到这一点。但首先,让我们看一下静态导入和动态导入之间的区别:
// static import
import Message from "./Message";
// dynamic import
import("./Message").then(Message => {
// Message module is available here...
});
现在,让我们将这些知识应用到我们的Message组件中,我们将得到一个App.vue如下所示的组件:
<!-- App.vue -->
<template>
<div>
<message></message>
</div>
</template>
<script>
import Message from "./Message";
export default {
components: {
Message: () => import("./Message")
}
};
</script>
如您所见,该import()函数将解析一个返回组件的 Promise,这意味着我们已经成功地异步加载了我们的组件。如果您查看 devtools 的网络选项卡,您会注意到一个名为的文件0.js,其中包含您的异步组件。
有条件地加载异步组件
现在我们已经掌握了异步组件,让我们通过仅在真正需要时加载它们来真正收获它们的力量。在本文的前一部分中,我解释了仅当用户点击结帐按钮时才加载的结帐框的用例。让我们构建它。
项目设置
如果你没有安装 Vue CLI,你现在应该安装它:
npm i -g @vue/cli
接下来,使用 CLI 创建一个新项目,在出现提示时选择默认预设:
vue create my-store
切换到项目目录,然后安装我们将用于样式化的ant-design-vue库:
cd my-store
npm i ant-design-vue
接下来,导入 Ant Design 库src/main.js:
import 'ant-design-vue/dist/antd.css'
最后,在src/comonents,Checkout.vue和中创建两个新组件Items.vue:
touch src/components/{Checkout.vue,Items.vue}
制作商店视图{#makingthestoreview}
打开src/App.vue并用以下代码替换那里的代码:
<template>
<div id="app">
<h1>{{ msg }}</h1>
<items></items>
</div>
</template>
<script>
import items from "./components/Items"
export default {
components: {
items
},
name: 'app',
data () {
return {
msg: 'My Fancy T-Shirt Store'
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>
这里没什么特别的。我们所做的只是显示一条消息并呈现一个<items>组件。
接下来,打开src/components/Items.vue并添加以下代码:
<template>
<div>
<div style="padding: 20px;">
<Row :gutter="16">
<Col :span="24" style="padding:5px">
<Icon type="shopping-cart" style="margin-right:5px"/>{{shoppingList.length}} item(s)
<Button @click="show = true" id="checkout">Checkout</Button>
</Col>
</Row>
</div>
<div v-if="show">
<Row :gutter="16" style="margin:0 400px 50px 400px">
<checkout v-bind:shoppingList="shoppingList"></checkout>
</Row>
</div>
<div style="background-color: #ececec; padding: 20px;">
<Row :gutter="16">
<Col :span="6" v-for="(item, key) in items" v-bind:key="key" style="padding:5px">
<Card v-bind:title="item.msg" v-bind:key="key">
<Button type="primary" @click="addItem(key)">Buy ${{item.price}}</Button>
</Card>
</Col>
</Row>
</div>
</div>
</template>
<script>
import { Card, Col, Row, Button, Icon } from 'ant-design-vue';
export default {
methods: {
addItem (key) {
if(!this.shoppingList.includes(key)) {
this.shoppingList.push(key);
}
}
},
components: {
Card, Col, Row, Button, Icon,
checkout: () => import('./Checkout')
},
data: () => ({
items: [
{ msg: 'First Product', price: 9.99 },
{ msg: 'Second Product', price: 19.99 },
{ msg: 'Third Product', price: 15.00 },
{ msg: 'Fancy Shirt', price: 137.00 },
{ msg: 'More Fancy', price: 109.99 },
{ msg: 'Extreme', price: 3.00 },
{ msg: 'Super Shirt', price: 109.99 },
{ msg: 'Epic Shirt', price: 3.00 },
],
shoppingList: [],
show: false
})
}
</script>
<style>
#checkout {
background-color:#e55242;
color:white;
margin-left: 10px;
}
</style>
在这个文件中,我们显示了一个购物车图标,其中包含当前购买的商品数量。这些项目本身是从items数组中提取的,声明为数据属性。如果您单击某个项目的"购买"按钮,addItem则会调用该方法,该方法会将有问题的项目推送到一个shoppingList数组中。反过来,这将增加购物车的总数。
我们还在页面上添加了一个Checkout按钮,这就是事情开始变得有趣的地方:
<Button @click="show = true" id="checkout">Checkout</Button>
在这个文件中,我们显示了一个购物车图标,其中包含当前购买的商品数量。这些项目本身是从items数组中提取的,声明为数据属性。如果您单击某个项目的"购买"按钮,addItem则会调用该方法,该方法会将有问题的项目推送到一个shoppingList数组中。反过来,这将增加购物车的总数。
我们还在页面上添加了一个Checkout按钮,这就是事情开始变得有趣的地方:
<Button @click="show = true" id="checkout">Checkout</Button>
当用户单击此按钮时,我们将参数设置show为true。这个true值对于有条件地加载我们的异步组件非常重要。
下面几行,你可以找到一条v-if语句,它只显示<div>whenshow设置为时的内容true。此<div>标记包含结帐组件,我们只想在用户点击结帐按钮时加载它。
components结帐组件在该部分的选项中异步加载<script>。这里很酷的是我们甚至可以通过v-bind语句将参数传递给组件。如您所见,创建条件异步组件相对容易:
<div v-if="show">
<checkout v-bind:shoppingList="shoppingList"></checkout>
</div>
让我们快速添加Checkout组件的代码src/components/Checkout.vue:
<template>
<Card title="Checkout Items" key="checkout">
<p v-for="(k, i) in this.shoppingList" :key="i">
Item: {{items[Number(k)].msg}} for ${{items[Number(k)].price}}
</p>
</Card>
</template>
<script>
import { Card } from 'ant-design-vue';
export default {
props: ['shoppingList'],
components: {
Card
},
data: () => ({
items: [
{ msg: 'First Product', price: 9.99 },
{ msg: 'Second Product', price: 19.99 },
{ msg: 'Third Product', price: 15.00 },
{ msg: 'Fancy Shirt', price: 137.00 },
{ msg: 'More Fancy', price: 109.99 },
{ msg: 'Extreme', price: 3.00 },
{ msg: 'Super Shirt', price: 109.99 },
{ msg: 'Epic Shirt', price: 3.00 },
]
})
}
</script>
在这里,我们循环遍历我们收到的 propsshoppingList并将它们输出到屏幕。
您可以使用npm run serve命令运行该应用程序。然后导航到http://localhost:8080/。如果一切都按计划进行,您应该会看到如下图所示的内容。
尝试在打开网络选项卡的情况下在商店中四处单击,以确保仅在单击"结帐"按钮Checkout时才加载该组件。
您还可以在 GitHub 上找到此演示的代码。
与加载和错误组件异步{#asyncwithloadinganderrorcomponent}
甚至可以为异步组件加载时间或加载失败定义加载和/或错误组件。显示加载动画可能很有用,但请记住,这会再次减慢您的应用程序。异步组件应该小且加载速度快。这是一个例子:
const Message = () => ({
component: import("./Message"),
loading: LoadingAnimation,
error: ErrorComponent
});
结论{#conclusion}
创建和实现异步组件非常容易,应该成为您标准开发程序的一部分。从用户体验的角度来看,尽可能减少初始加载时间以保持用户的注意力很重要。希望本教程已帮助您异步加载自己的组件并向它们应用条件以延迟(延迟加载)组件的加载。