特别声明,本文根据@Alex Jover Morales的《Async Vue.js Components》一文所整理。
随着应用程序越来越大,你开始考虑优化应用程序,使其变得更快。在此过程中,你可能使用了拆分代码和延迟加载这两种方法,它们通过将代码块的加截推迟到需要的时候加载,从而使应用程序的初始包变得更小。
延迟加载对于应用程序路由有很大的意义,并且有很大的影响,因为每个路由都是应用程序的不同部分。
延迟加载有意义的另一种情况是组件延迟渲染。这些组件可以是tooltips
、popover
、modal
等,当然这些组件也可以使用异步组件。
让我们来看看如何在Vue中构建延迟加载异步组件。
延迟加载组件
在我们开始了解延迟加载组件之前,我们先来了解通常是如何加载组件的。 为此,我创建了一个Tooltip.vue
组件:
<!-- Tooltip.vue -->
<template>
<h1>Hi from Tooltip!</h2>
</template>
这里没有什么特别之处,它就是一个简单的组件。我们可以通过本地注册,导入Tooltip
组件并将其添加到components
选项中,这样就可以在另一个组件中使用它。比如,在App.vue
中使用它:
<!-- App.vue -->
<template>
<div id="app">
<Tooltip />
</div>
</template>
<script>
import Tooltip from "./components/Tooltip"
export default {
name: "App",
components: {
Tooltip
}
};
</script>
只要App
被导入,就可以在初始加载时,Tooltip
组件就会被导入、使用和加载。但是想想:只有在我们要使用组件时才加载该组件难道没有意义吗?用户很可能在不需要工具提示的情况下浏览整个系统。
为什么我们要在应用程序开始时花费宝贵的资源来加载组件呢?我们可以应用延迟加载和代码拆分来改进它。延迟加载是在稍后的阶段加载某些内容的技术。
虽然代码拆分是将一段代码拆分到一个单独的文件(称为chunk)中,以便减少应用程序的初始包,从而减轻初始加载。
通过使用动态导入(Dynamic import),Vue可以轻松应用这些技术。在ES2018中也会具有这样的功能,它允许程序在运行时加载模块。我闪将有一篇文章深入探讨这些概念,但让我们从实用和简单的角度开始吧。
现代的绑定器(Modern bundlers),比如Webpack(从版本2开始),Rollup和Parcel将理解这种语法并自动为该模块创建一个单独的文件,该文件将在需要时加载。
我在这里认为你已经熟悉了静态方式导入模块。然而,动态导入是一个返回Promise
的函数,其中包含模块作为其有效的加载。下面的示例展示了如何以静态导入和动态方式导入utils
模块。
// 静态导入模块
import utils from './utils'
// 动态导入
import('./utils').then(utils => {
// 可以在这里使用utils模块
})
在Vue中延迟加载组件与在封装的函数中动态导入组件一样容易。在前面的例子中,我们可以像下面这样延迟加载Tooltip
组件:
export default {
components: {
Tooltip: () => import('./components/Tooltip')
}
}
使用() => import('./components/Tooltip')
替代前面示例中的import Tooltip from "./components/Tooltip"
。Vue一旦请求渲染将会延迟加载该组件。
不仅如此,它还将应用代码拆分。你可以使用上面提到的任何绑定器运行代码来进行测试。最简单的方式就是使用vue-cil,但在文章的最后,你将找到一个已经构建好的Demo。运行后,打开开发者工具,在Network一栏将可可以看到一个名为1.chunk.js
这样的JavaScript文件。
有条件地加载一个异步组件
在前面的示例中,尽管我们通过延迟加载来加载Tooltip
组件,但它将在需要渲染时立即加载,这在App
组件加载时就立即发生了。
然而,在实践中,我们希望将Tooltip
组件加载能延迟到需要时加载,这通常是在触发某个事件之后有条件地进行,比如在按钮或文本上悬停时触发。
为了简单起见,在App
组件中添加一个按钮,使用v-if
有条件地渲染Tooltip
组件:
<!-- App.vue -->
<template>
<div>
<button @click="show = true">Load Tooltip</button>
<div v-if="show">
<Tooltip />
</div>
</div>
</template>
<script>
export default {
data: () => ({
show: false
}),
components: {
Tooltip: () => import('./components/Tooltip')
}
}
</script>
请记住,Vue在需要渲染之前不会使用该组件。这意味着在点击之前不需要该组件,并且该组件将被延迟加载。
异步组件的用户体验
大多数情况下,异步组件加载速度非常快,因为它们是从主包中拆分出来的小块代码。但是想象一下,你在一个非常慢的网络环境下缓慢的加载一个大的模态(Modal)组件。这可能需要一些时间来加载和渲染。
当然,你可以使用一些优化,比如HTTP缓存或资源提示,以低优先级预加载到内存中。事实上,新的vue-cli会对这些延迟加载的块预先获取。不过,在一些情况下,加载可能需要一些时间。
从用户体验的角度来看,如果一个任务需要超过1s
的时间,你就会开始失去用户的注意力。
但是,可以通过向用户提供反馈来保持注意力。为了吸引用户的注意力,我们可以在加载时使用progress
(进度条)组件,但是在异步加载时,我们如何使用一个漂亮的loading
或progress
组件呢?
加载组件
你还记得我们使用一个带有动态导入的函数来延迟加载异步组件吗?
export default {
components: {
Tooltip: () => import('./components/Tooltip')
}
}
通过返回对象而不是动态导入的结果来定义异步组件长期的方法。在该对象中,我们可以定个一个加载组件:
const Tooltip = () => ({
component: import('./components/Tooltip'),
loading: AwesomeSpinner
})
这样,在默认延迟200ms
之后,组件AwesomeSpinner
就会显示出来。你也可以自定义延迟时间:
const Tooltip = () => ({
component: import('./components/Tooltip'),
loading: AwesomeSpinner,
delay: 500
})
作为加载组件应该使用的组件必须尽可能的小,以使它几乎能立即加载
错误组件
同样的,我们可以用延迟加载组件的方式来定义一个错误组件:
const Tooltip = () => ({
component: import('./components/Tooltip'),
loading: AwesomeSpinner,
error: SadFaceComponent
})
加载./components/Tooltip
组件出错时,SadFaceComponent
组件将会显示。在下面这几种情况之下可能会发生这种情况:
- 网络瘫痪(连不上网)
- 该组件不存在(这是一种尝试它的好方法,你可以自己故意删除它)
- 加载超时
默认情况下,没有超时,但我们可以自己配置:
const Tooltip = () => ({
component: import('./components/Tooltip'),
loading: AwesomeSpinner,
error: SadFaceComponent,
timeout: 5000
})
现在,如果在5000ms
之后Tooltip
组件还未加载,将会显示SadFaceComponent
组件。
总结
你已经了解了如何在自己的块文件中分割组件,以及如何使用动态导入来延迟加载组件。我们还通过有条件地渲染数据来延迟数据块的加载。
虽然异步组件可以通过分割和延迟的加载方式来提高应用程序的加载时间,但它们可能会对用户体验有很大的影响,尤其是当它们很大的时候。控制加载状态允许我们提供反馈,并在速度很慢的情况下让用户参与进来。