如何优化 Vue.js 应用程序

如何,优化,vue,js,应用程序 · 浏览次数 : 506

小编点评

**SPA网络应用程序最佳化指南** **性能优化** * **使用SWRV库对组件状态进行处理:**SWRV可以帮助减少组件渲染时之间的延迟。 * **优化HTTP响应:**使用`cache-control`头可以缓存数据,减少网络请求。 * **使用`Transition`组件进行动画:**`Transition`组件可以帮助使组件的渲染更流畅。 * **减少组件渲染次数:**使用`computed`属性和`memo`函数可以避免在组件中重复渲染。 **性能优化方法** * **使用`LazyLoad`组件加载图片:**`LazyLoad`组件可以帮助减少组件渲染时之间的延迟。 * **使用`abort`控制器进行网络请求中止:**`abort`控制器可以帮助减少网络请求的延迟。 * **优化`useLazyFetch`组件:**`useLazyFetch`组件可以帮助减少组件渲染时之间的延迟。 * **使用`computed`属性和`memo`函数优化组件状态:**`computed`属性和`memo`函数可以帮助避免在组件中重复渲染。 * **使用`transition`组件进行动画:**`transition`组件可以帮助使组件的渲染更流畅。 **其他优化** * **使用`Vue`内置的`Transition`组件进行动画:**`Transition`组件可以帮助使组件的渲染更流畅。 * **使用```````````````````````````````

正文

单页面应用(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函数成功返回时导入组件:

<template>
  <div>
    <component :is="KPIComponent" :data="data"></component>
  </div>
</template>

<script>
import {
  defineComponent,
  ref,
  defineAsyncComponent,
} from "vue";
import Loader from "./Loader";
import Error from "./Error";

export default defineComponent({
    components: { Loader, Error },

    setup() {
        const data = ref(null);

        const loadComponent = () => {
          return fetch('<https://api.npoint.io/ec46e59905dc0011b7f4>')
            .then((response) => response.json())
            .then((response) => (data.value = response))
            .then(() => import("../components/KPI.vue") // Import the component
            .catch((e) => console.error(e));
        };

        const KPIComponent = defineAsyncComponent({
          loader: loadComponent,
          loadingComponent: Loader,
          errorComponent: Error,
          delay: 200,
          timeout: 5000,
        });

        return { data, KPIComponent };
    }
}

该模式可以扩展到应用程序的任意地方,组件在用户交互后进行渲染。比如说,当用户点击Map标签时,加载map组件以及相关依赖。

CSS

除了动态导入JS模块外,在组件的<style>块中导入依赖也会懒加载CSS:

// In MapView.vue
<style>
@import "../../node_modules/leaflet/dist/leaflet.css";

.map-wrapper {
  aspect-ratio: 16 / 9;
}
</style>

完善加载状态

在这一点上,我们的API请求是并行运行的,组件在不同时间被渲染。可能会注意到一件事,那就是页面看起来很糟糕,因为布局会有很大的变化。

一个让用户感觉更顺畅的快速方法,是在部件上设置一个与渲染的组件大致对应的长宽比,这样用户就不会看到那么大的布局变化。我们可以传入一个参数以考虑到不同的组件,并用一个默认值来回退。

// WidgetLoader.vue
<template>
  <div class="widget" :style="{ 'aspect-ratio': loading ? aspectRatio : '' }">
    <component :is="AsyncComponent" :data="data"></component>
  </div>
</template>

<script>
import { defineComponent, ref, onBeforeMount, onBeforeUnmount } from "vue";
import Loader from "./Loader";
import Error from "./Error";

export default defineComponent({
  components: { Loader, Error },

  props: {
    aspectRatio: {
      type: String,
      default: "5 / 3", // define a default value
    },
    url: String,
    importFunction: Function,
  },

  setup(props) {
      const data = ref(null);
      const loading = ref(true);

        const loadComponent = () => {
          return fetch(url)
            .then((response) => response.json())
            .then((response) => (data.value = response))
            .then(importFunction
            .catch((e) => console.error(e))
            .finally(() => (loading.value = false)); // Set the loading state to false
        };

    /* ...Rest of the component code */

    return { data, aspectRatio, loading };
  },
});
</script>

取消API请求

在一个有大量API请求的页面上,如果用户在所有请求还没有完成时离开页面,会发生什么?我们可能不想这些请求继续在后台运行,拖慢了用户体验。

我们可以使用AbortController接口,这使我们能够根据需要中止API请求。

setup函数中,我们创建一个新的controller,并传递signalfetch请求参数中:

setup(props) {
    const controller = new AbortController();

    const loadComponent = () => {
      return fetch(url, { signal: controller.signal })
        .then((response) => response.json())
        .then((response) => (data.value = response))
        .then(importFunction)
        .catch((e) => console.error(e))
        .finally(() => (loading.value = false));
        };
}

然后我们使用Vue的onBeforeUnmount函数,在组件被卸载之前中止请求:

onBeforeUnmount(() => controller.abort());

如果你运行该项目并在请求完成之前导航到另一个页面,你应该看到控制台中记录的错误,说明请求已经被中止。

Stale While Revalidate

目前为止,我们已经做了相当好的一部分优化。但是当用户前往下个页面后,然后返回上一页,所有的组件都会重新挂载,并返回自身的加载状态,我们又必须再次等待请求有所响应。

Stale-while-revalidate是一种HTTP缓存失效策略,浏览器决定是在内容仍然新鲜的情况下从缓存中提供响应,还是在响应过期的情况下"重新验证 "并从网络上提供响应。

除了在我们的HTTP响应中应用cache-control头部(不在本文范围内,但可以阅读Web.dev的这篇文章以了解更多细节),我们可以使用SWRV库对我们的Vue组件状态应用类似的策略。

首先,我们必须从SWRV库中导入组合式内容:

import useSWRV from "swrv";

然后,我们可以在setup函数使用它。我们把loadComponent函数改名为fetchData,因为它将只处理数据的获取。我们将不再在这个函数中导入我们的组件,因为我们将单独处理这个问题。

我们将把它作为第二个参数传入useSWRV函数调用。只有当我们需要一个自定义函数来获取数据时,我们才需要这样做(也许我们需要更新一些其他的状态片段)。因为我们使用的是Abort Controller,所以我们要这样做;否则,第二个参数可以省略,SWRV将使用Fetch API:

// In setup()
const { url, importFunction } = props;

const controller = new AbortController();

const fetchData = () => {
  return fetch(url, { signal: controller.signal })
    .then((response) => response.json())
    .then((response) => (data.value = response))
    .catch((e) => (error.value = e));
};

const { data, isValidating, error } = useSWRV(url, fetchData);

然后我们将从我们的异步组件定义中删除loadingComponenterrorComponent选项,因为我们将使用SWRV来处理错误和加载状态。

// In setup()
const AsyncComponent = defineAsyncComponent({
  loader: importFunction,
  delay: 200,
  timeout: 5000,
});

这意味着,我们需要在模板文件中包含LoaderError组件,展示或隐藏取决于状态。isValidating的返回值告诉我们是否有一个请求或重新验证发生。

<template>
  <div>
    <Loader v-if="isValidating && !data"></Loader>
    <Error v-else-if="error" :errorMessage="error.message"></Error>
    <component :is="AsyncComponent" :data="data" v-else></component>
  </div>
</template>

<script>
import {
  defineComponent,
  defineAsyncComponent,
} from "vue";
import useSWRV from "swrv";

export default defineComponent({
  components: {
    Error,
    Loader,
  },

  props: {
    url: String,
    importFunction: Function,
  },

  setup(props) {
    const { url, importFunction } = props;

    const controller = new AbortController();

    const fetchData = () => {
      return fetch(url, { signal: controller.signal })
        .then((response) => response.json())
        .then((response) => (data.value = response))
        .catch((e) => (error.value = e));
    };

    const { data, isValidating, error } = useSWRV(url, fetchData);

    const AsyncComponent = defineAsyncComponent({
      loader: importFunction,
      delay: 200,
      timeout: 5000,
    });

    onBeforeUnmount(() => controller.abort());

    return {
      AsyncComponent,
      isValidating,
      data,
      error,
    };
  },
});
</script>

我们可以将其重构为自己的组合式代码,使我们的代码更简洁一些,并使我们能够在任何地方使用它。

// composables/lazyFetch.js
import { onBeforeUnmount } from "vue";
import useSWRV from "swrv";

export function useLazyFetch(url) {
  const controller = new AbortController();

  const fetchData = () => {
    return fetch(url, { signal: controller.signal })
      .then((response) => response.json())
      .then((response) => (data.value = response))
      .catch((e) => (error.value = e));
  };

  const { data, isValidating, error } = useSWRV(url, fetchData);

  onBeforeUnmount(() => controller.abort());

  return {
    isValidating,
    data,
    error,
  };
}
// WidgetLoader.vue
<script>
import { defineComponent, defineAsyncComponent, computed } from "vue";
import Loader from "./Loader";
import Error from "./Error";
import { useLazyFetch } from "../composables/lazyFetch";

export default defineComponent({
  components: {
    Error,
    Loader,
  },

  props: {
    aspectRatio: {
      type: String,
      default: "5 / 3",
    },
    url: String,
    importFunction: Function,
  },

  setup(props) {
    const { aspectRatio, url, importFunction } = props;
    const { data, isValidating, error } = useLazyFetch(url);

    const AsyncComponent = defineAsyncComponent({
      loader: importFunction,
      delay: 200,
      timeout: 5000,
    });

    return {
      aspectRatio,
      AsyncComponent,
      isValidating,
      data,
      error,
    };
  },
});
</script>

更新指示

如果我们能在我们的请求重新验证的时候向用户显示一个指示器,这样他们就知道应用程序正在检查新的数据,这可能会很有用。在这个例子中,我在组件的角落里添加了一个小的加载指示器,只有在已经有数据,但组件正在检查更新时才会显示。我还在组件上添加了一个简单的fade-in过渡(使用Vue内置的Transition组件),所以当组件被渲染时,不会有突兀的跳跃。

<template>
  <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>

    <!--Indicator if data is updating-->
    <Loader
      v-if="isValidating && data"
      text=""
    ></Loader>
  </div>
</template>

总结

在建立我们的网络应用程序时,优先考虑性能,可以提高用户体验,并有助于确保它们可以被尽可能多的人使用。我希望这篇文章提供了一些关于如何使你的应用程序尽可能高效的观点--无论你是选择全部还是部分地实施它们。

SPA可以工作得很好,但它们也可能成为性能瓶颈。所以,让我们试着把它们变得更好。

以上就是本文的全部内容,如果帮助到了你,欢迎点赞、收藏、转发~

与如何优化 Vue.js 应用程序相似的内容:

如何优化 Vue.js 应用程序

单页面应用(SPAs)当处理实时、异步数据时,可以提供丰富的、可交互的用户体验。但它们也可能很重,很臃肿,而且性能很差。在这篇文章中,我们将介绍一些前端优化技巧,以保持我们的Vue应用程序相对精简,并且只在需要的时候提供必需的JS。 注意:这里假设你对Vue和Composition API有一定的熟

Vue Router 4与路由管理实战

这篇文章介绍了如何在Vue.js应用中利用Vue Router实现单页面应用的路由管理,包括配置路由、导航守卫的使用、路由懒加载以优化性能以及动态路由的实现方法,旨在提升用户体验和应用加载效率

Vue微前端架构与Qiankun实践理论指南

这篇文章介绍了微前端架构概念,聚焦于如何在Vue.js项目中应用Qiankun框架实现模块化和组件化,以达到高效开发和维护的目的。讨论了Qiankun的原理、如何设置主应用与子应用的通信,以及如何解决跨域问题和优化集成过程,从而实现前端应用的灵活扩展与组织。

Vue第三方库与插件实战手册

这篇文章介绍了如何在Vue框架中实现数据的高效验证与处理,以及如何集成ECharts、D3.js、Chart.js等图表库优化数据可视化效果。同时,探讨了Progressive Web App(PWA)的接入与优化策略,以提升Web应用的用户体验与加载速度。

微前端框架single-spa子应用加载解析

本文主要通过对微前端框架single-spa的基座应用加载子应用的single-spa-vue函数库进行分析,通过代码维度分析让大家了解在single-spa加载子应用的时候都做了哪些事情。如何通过优化single-spa-vue函数库保持子应用的状态。

Vue CLI 4与项目构建实战指南

这篇文章介绍了如何使用Vue CLI优化项目构建配置,提高开发效率,涉及配置管理、项目部署策略、插件系统定制以及Webpack和TypeScript的深度集成技巧。

Vue 3深度探索:自定义渲染器与服务端渲染

这篇文章介绍了如何在Vue框架中实现自定义渲染器以增强组件功能,探讨了虚拟DOM的工作原理,以及如何通过SSR和服务端预取数据优化首屏加载速度。同时,讲解了同构应用的开发方式与状态管理技巧,助力构建高性能前端应用。

『手撕Vue-CLI』获取下载目录

开篇 在上一篇文章中,简单的对 Nue-CLI 的代码通过函数柯里化优化了一下,这一次来实现一个获取下载目录的功能。 背景 在 Nue-CLI 中,我现在实现的是 create 指令,这个指令本质就是首先拿到模板名称和版本号之后,然后去进行下载对应的模板,关于下载那么肯定要面临的问题就是如何下载?下

vue3编译优化之“静态提升”

前言 在上一篇 vue3早已具备抛弃虚拟DOM的能力了文章中讲了对于动态节点,vue做的优化是将这些动态节点收集起来,然后当响应式变量修改后进行靶向更新。那么vue对静态节点有没有做什么优化呢?答案是:当然有,对于静态节点会进行“静态提升”。这篇文章我们来看看vue是如何进行静态提升的。 什么是静态

初学者必读:如何使用 Nuxt 中间件简化网站开发

本文概述了Nuxt 3框架的升级特点,对比Nuxt 2,详细解析中间件应用、配置策略与实战示例,涵盖功能、错误管理、优化技巧,并探讨与Nuxt 3核心组件集成方法,给出最佳实践和问题解决方案,强调利用Vue 3和Serverless Functions提升中间件效能。