单页面应用(SPAs)当处理实时、异步数据时,可以提供丰富的、可交互的用户体验。但它们也可能很重,很臃肿,而且性能很差。在这篇文章中,我们将介绍一些前端优化技巧,以保持我们的Vue应用程序相对精简,并且只在需要的时候提供必需的JS。
注意:这里假设你对Vue和Composition API有一定的熟悉程度,但无论你选择哪种框架,都希望能有一些收获。
本文作者是一名前端开发工程师,职责是构建Windscope应用程序。下面介绍基于该程序所做的一系列优化。
选择框架
我们选择的JS框架是Vue,部分原因是它是我最熟悉的框架。以前,Vue与React相比,整体包规模较小。然而,自从最近的React更新以来,平衡似乎已经转移到React身上。这并不重要,因为我们将在本文中研究如何只导入我们需要的东西。这两个框架都有优秀的文档和庞大的开发者生态系统,这是另一个考虑因素。Svelte是另一个可能的选择,但由于不熟悉,它需要更陡峭的学习曲线,而且由于较新,它的生态系统不太发达。
Vue Composition API
Vue 3引入了Composition API,这是一套新的API用于编写组件,作为Options API的替代。通过专门使用Composition API,我们可以只导入我们需要的Vue函数,而不是整个包。它还使我们能够使用组合式函数编写更多可重用的代码。使用Composition API编写的代码更适合于最小化,而且整个应用程序更容易受到tree-shaking的影响。
注意:如果你正在使用较老版本的Vue,仍然可以使用Composition API:它已被补丁到Vue 2.7,并且有一个适用于旧版本的官方插件。
导入依赖
一个关键目标是减少通过客户端下载的初始JS包的尺寸。Windscope广泛使用D3进行数据可视化,这是一个庞大的库,范围很广。然而,Windscope只需要使用D3的一部分。
让我们的依赖包尽可能小的一个最简单的方法是,只导入我们需要的模块。
让我们来看看D3的selectAll函数。我们可以不使用默认导入,而只从d3-selection模块中导入我们需要的函数:
// Previous:
import * as d3 from 'd3'
// Instead:
import { selectAll } from 'd3-selection'
代码分割
有一些包在整个Windscope的很多地方都有使用,比如AWS Amplify认证库,特别是Auth方法。这是一个很大的依赖,对我们的JS包的大小有很大贡献。比起在文件顶部静态导入模块,动态导入允许我们在代码中需要的地方准确导入模块。
比起这么导入:
import { Auth } from '@aws-amplify/auth'
const user = Auth.currentAuthenticatedUser()
我们可以在想要使用它的地方导入模块:
import('@aws-amplify/auth').then(({ Auth }) => {
const user = Auth.currentAuthenticatedUser()
})
这意味着该模块将被分割成一个单独的JS包(或 "块"),只有该模块被使用时,才会被浏览器下载。
除此之外,浏览器可以缓存这些依赖,比起应用程序的其他部分代码,这些模块基本不会改变。
懒加载
我们的应用程序使用Vue Router作为导航路由。与动态导入类似,我们可以懒加载我们的路由组件,这样就可以在用户导航到路由时,它们才会被导入(连同其相关的依赖关系)。
index/router.js文件:
// Previously:
import Home from "../routes/Home.vue";
import About = "../routes/About.vue";
// Lazyload the route components instead:
const Home = () => import("../routes/Home.vue");
const About = () => import("../routes/About.vue");
const routes = [
{
name: "home",
path: "/",
component: Home,
},
{
name: "about",
path: "/about",
component: About,
},
];
当用户点击About链接并导航到路由时,About路由所对应的代码才会被加载。
异步组件
除了懒加载每个路由外,我们还可以使用Vue的defineAsyncComponent方法懒加载单个组件。
const KPIComponent = defineAsyncComponent(() => import('../components/KPI.vue'))
这意味着KPI组件的代码会被异步导入,正如我们在路由示例中看到的那样。当组件正在加载或者处于错误状态时,我们也可以提供展示的组件(这个在加载特别大的文件时非常有用)。
const KPIComponent = defineAsyncComponent({
loader: () => import('../components/KPI.vue'),
loadingComponent: Loader,
errorComponent: Error,
delay: 200,
timeout: 5000,
});
分割API请求
我们的应用程序主要关注的是数据可视化,并在很大程度上依赖于从服务器获取大量的数据。其中一些请求可能相当慢,因为服务器必须对数据进行一些计算。在最初的原型中,我们对每个路由的REST API进行了一次请求。不幸地是,我们发现这会导致用户必须等待很长时间。
我们决定将API分成几个端点,为每个部件发出请求。虽然这可能会增加整体的响应时间,但这意味着应用程序应该更快可用,因为用户将看到页面的一部分被渲染,而他们仍在等待其他部分。此外,任何可能发生的错误都会被本地化,而页面的其他部分仍然可以使用。
有条件加载组件
现在我们可以把它和异步组件结合起来,只在我们收到服务器的成功响应时才加载一个组件。下面示例中我们获取数据,然后在fetch函数成功返回时导入组件:
<div>
<component :is="KPIComponent" :data="data">component>
div>
更新指示
如果我们能在我们的请求重新验证的时候向用户显示一个指示器,这样他们就知道应用程序正在检查新的数据,这可能会很有用。在这个例子中,我在组件的角落里添加了一个小的加载指示器,只有在已经有数据,但组件正在检查更新时才会显示。我还在组件上添加了一个简单的fade-in过渡(使用Vue内置的Transition组件),所以当组件被渲染时,不会有突兀的跳跃。
<div
class="widget"
:style="{ 'aspect-ratio': isValidating && !data ? aspectRatio : '' }"
>
<Loader v-if="isValidating && !data">Loader>
<Error v-else-if="error" :errorMessage="error.message">Error>
<Transition>
<component :is="AsyncComponent" :data="data" v-else>component>
Transition>
<Loader
v-if="isValidating && data"
text=""
>Loader>
div>
总结
在建立我们的网络应用程序时,优先考虑性能,可以提高用户体验,并有助于确保它们可以被尽可能多的人使用。我希望这篇文章提供了一些关于如何使你的应用程序尽可能高效的观点--无论你是选择全部还是部分地实施它们。
SPA可以工作得很好,但它们也可能成为性能瓶颈。所以,让我们试着把它们变得更好。
以上就是本文的全部内容,如果帮助到了你,欢迎点赞、收藏、转发~