欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

教你如何優(yōu)化?Vue.js?應(yīng)用程序

 更新時間:2023年02月17日 08:37:32   作者:chuckQu  
Vue?3引入了Composition?API,這是一套新的API用于編寫組件,作為Options?API的替代,這篇文章主要介紹了如何優(yōu)化?Vue.js?應(yīng)用程序,需要的朋友可以參考下

單頁面應(yīng)用(SPAs)當(dāng)處理實時、異步數(shù)據(jù)時,可以提供豐富的、可交互的用戶體驗。但它們也可能很重,很臃腫,而且性能很差。在這篇文章中,我們將介紹一些前端優(yōu)化技巧,以保持我們的Vue應(yīng)用程序相對精簡,并且只在需要的時候提供必需的JS。

注意:這里假設(shè)你對Vue和Composition API有一定的熟悉程度,但無論你選擇哪種框架,都希望能有一些收獲。

本文作者是一名前端開發(fā)工程師,職責(zé)是構(gòu)建Windscope應(yīng)用程序。下面介紹基于該程序所做的一系列優(yōu)化。

選擇框架

我們選擇的JS框架是Vue,部分原因是它是我最熟悉的框架。以前,Vue與React相比,整體包規(guī)模較小。然而,自從最近的React更新以來,平衡似乎已經(jīng)轉(zhuǎn)移到React身上。這并不重要,因為我們將在本文中研究如何只導(dǎo)入我們需要的東西。這兩個框架都有優(yōu)秀的文檔和龐大的開發(fā)者生態(tài)系統(tǒng),這是另一個考慮因素。Svelte是另一個可能的選擇,但由于不熟悉,它需要更陡峭的學(xué)習(xí)曲線,而且由于較新,它的生態(tài)系統(tǒng)不太發(fā)達。

Vue Composition API

Vue 3引入了Composition API,這是一套新的API用于編寫組件,作為Options API的替代。通過專門使用Composition API,我們可以只導(dǎo)入我們需要的Vue函數(shù),而不是整個包。它還使我們能夠使用組合式函數(shù)編寫更多可重用的代碼。使用Composition API編寫的代碼更適合于最小化,而且整個應(yīng)用程序更容易受到tree-shaking的影響。

注意:如果你正在使用較老版本的Vue,仍然可以使用Composition API:它已被補丁到Vue 2.7,并且有一個適用于舊版本的官方插件。

導(dǎo)入依賴

一個關(guān)鍵目標(biāo)是減少通過客戶端下載的初始JS包的尺寸。Windscope廣泛使用D3進行數(shù)據(jù)可視化,這是一個龐大的庫,范圍很廣。然而,Windscope只需要使用D3的一部分。

讓我們的依賴包盡可能小的一個最簡單的方法是,只導(dǎo)入我們需要的模塊。

讓我們來看看D3的selectAll函數(shù)。我們可以不使用默認(rèn)導(dǎo)入,而只從d3-selection模塊中導(dǎo)入我們需要的函數(shù):

// Previous:
import * as d3 from 'd3'

// Instead:
import { selectAll } from 'd3-selection'

代碼分割

有一些包在整個Windscope的很多地方都有使用,比如AWS Amplify認(rèn)證庫,特別是Auth方法。這是一個很大的依賴,對我們的JS包的大小有很大貢獻。比起在文件頂部靜態(tài)導(dǎo)入模塊,動態(tài)導(dǎo)入允許我們在代碼中需要的地方準(zhǔn)確導(dǎo)入模塊。

比起這么導(dǎo)入:

import { Auth } from '@aws-amplify/auth'
const user = Auth.currentAuthenticatedUser()

我們可以在想要使用它的地方導(dǎo)入模塊:

import('@aws-amplify/auth').then(({ Auth }) => {
    const user = Auth.currentAuthenticatedUser()
})

這意味著該模塊將被分割成一個單獨的JS包(或 "塊"),只有該模塊被使用時,才會被瀏覽器下載。

除此之外,瀏覽器可以緩存這些依賴,比起應(yīng)用程序的其他部分代碼,這些模塊基本不會改變。

懶加載

我們的應(yīng)用程序使用Vue Router作為導(dǎo)航路由。與動態(tài)導(dǎo)入類似,我們可以懶加載我們的路由組件,這樣就可以在用戶導(dǎo)航到路由時,它們才會被導(dǎo)入(連同其相關(guān)的依賴關(guān)系)。

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,
  },
];

當(dāng)用戶點擊About鏈接并導(dǎo)航到路由時,About路由所對應(yīng)的代碼才會被加載。

異步組件

除了懶加載每個路由外,我們還可以使用Vue的defineAsyncComponent方法懶加載單個組件。

const KPIComponent = defineAsyncComponent(() => import('../components/KPI.vue'))

這意味著KPI組件的代碼會被異步導(dǎo)入,正如我們在路由示例中看到的那樣。當(dāng)組件正在加載或者處于錯誤狀態(tài)時,我們也可以提供展示的組件(這個在加載特別大的文件時非常有用)。

const KPIComponent = defineAsyncComponent({
  loader: () => import('../components/KPI.vue'),
  loadingComponent: Loader,
  errorComponent: Error,
  delay: 200,
  timeout: 5000,
});

分割A(yù)PI請求

我們的應(yīng)用程序主要關(guān)注的是數(shù)據(jù)可視化,并在很大程度上依賴于從服務(wù)器獲取大量的數(shù)據(jù)。其中一些請求可能相當(dāng)慢,因為服務(wù)器必須對數(shù)據(jù)進行一些計算。在最初的原型中,我們對每個路由的REST API進行了一次請求。不幸地是,我們發(fā)現(xiàn)這會導(dǎo)致用戶必須等待很長時間。

我們決定將API分成幾個端點,為每個部件發(fā)出請求。雖然這可能會增加整體的響應(yīng)時間,但這意味著應(yīng)用程序應(yīng)該更快可用,因為用戶將看到頁面的一部分被渲染,而他們?nèi)栽诘却渌糠帧4送?,任何可能發(fā)生的錯誤都會被本地化,而頁面的其他部分仍然可以使用。

有條件加載組件

現(xiàn)在我們可以把它和異步組件結(jié)合起來,只在我們收到服務(wù)器的成功響應(yīng)時才加載一個組件。下面示例中我們獲取數(shù)據(jù),然后在fetch函數(shù)成功返回時導(dǎo)入組件:

<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 };
    }
}

該模式可以擴展到應(yīng)用程序的任意地方,組件在用戶交互后進行渲染。比如說,當(dāng)用戶點擊Map標(biāo)簽時,加載map組件以及相關(guān)依賴。

CSS

除了動態(tài)導(dǎo)入JS模塊外,在組件的<style>塊中導(dǎo)入依賴也會懶加載CSS:

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

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

完善加載狀態(tài)

在這一點上,我們的API請求是并行運行的,組件在不同時間被渲染。可能會注意到一件事,那就是頁面看起來很糟糕,因為布局會有很大的變化。

一個讓用戶感覺更順暢的快速方法,是在部件上設(shè)置一個與渲染的組件大致對應(yīng)的長寬比,這樣用戶就不會看到那么大的布局變化。我們可以傳入一個參數(shù)以考慮到不同的組件,并用一個默認(rèn)值來回退。

// 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請求的頁面上,如果用戶在所有請求還沒有完成時離開頁面,會發(fā)生什么?我們可能不想這些請求繼續(xù)在后臺運行,拖慢了用戶體驗。

我們可以使用AbortController接口,這使我們能夠根據(jù)需要中止API請求。

setup函數(shù)中,我們創(chuàng)建一個新的controller,并傳遞signalfetch請求參數(shù)中:

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函數(shù),在組件被卸載之前中止請求:

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

如果你運行該項目并在請求完成之前導(dǎo)航到另一個頁面,你應(yīng)該看到控制臺中記錄的錯誤,說明請求已經(jīng)被中止。

Stale While Revalidate

目前為止,我們已經(jīng)做了相當(dāng)好的一部分優(yōu)化。但是當(dāng)用戶前往下個頁面后,然后返回上一頁,所有的組件都會重新掛載,并返回自身的加載狀態(tài),我們又必須再次等待請求有所響應(yīng)。

Stale-while-revalidate是一種HTTP緩存失效策略,瀏覽器決定是在內(nèi)容仍然新鮮的情況下從緩存中提供響應(yīng),還是在響應(yīng)過期的情況下"重新驗證 "并從網(wǎng)絡(luò)上提供響應(yīng)。

除了在我們的HTTP響應(yīng)中應(yīng)用cache-control頭部(不在本文范圍內(nèi),但可以閱讀Web.dev的這篇文章以了解更多細(xì)節(jié)),我們可以使用SWRV庫對我們的Vue組件狀態(tài)應(yīng)用類似的策略。

首先,我們必須從SWRV庫中導(dǎo)入組合式內(nèi)容:

import useSWRV from "swrv";

然后,我們可以在setup函數(shù)使用它。我們把loadComponent函數(shù)改名為fetchData,因為它將只處理數(shù)據(jù)的獲取。我們將不再在這個函數(shù)中導(dǎo)入我們的組件,因為我們將單獨處理這個問題。

我們將把它作為第二個參數(shù)傳入useSWRV函數(shù)調(diào)用。只有當(dāng)我們需要一個自定義函數(shù)來獲取數(shù)據(jù)時,我們才需要這樣做(也許我們需要更新一些其他的狀態(tài)片段)。因為我們使用的是Abort Controller,所以我們要這樣做;否則,第二個參數(shù)可以省略,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來處理錯誤和加載狀態(tài)。

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

這意味著,我們需要在模板文件中包含LoaderError組件,展示或隱藏取決于狀態(tài)。isValidating的返回值告訴我們是否有一個請求或重新驗證發(fā)生。

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

我們可以將其重構(gòu)為自己的組合式代碼,使我們的代碼更簡潔一些,并使我們能夠在任何地方使用它。

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

更新指示

如果我們能在我們的請求重新驗證的時候向用戶顯示一個指示器,這樣他們就知道應(yīng)用程序正在檢查新的數(shù)據(jù),這可能會很有用。在這個例子中,我在組件的角落里添加了一個小的加載指示器,只有在已經(jīng)有數(shù)據(jù),但組件正在檢查更新時才會顯示。我還在組件上添加了一個簡單的fade-in過渡(使用Vue內(nèi)置的Transition組件),所以當(dāng)組件被渲染時,不會有突兀的跳躍。

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

總結(jié)

在建立我們的網(wǎng)絡(luò)應(yīng)用程序時,優(yōu)先考慮性能,可以提高用戶體驗,并有助于確保它們可以被盡可能多的人使用。我希望這篇文章提供了一些關(guān)于如何使你的應(yīng)用程序盡可能高效的觀點--無論你是選擇全部還是部分地實施它們。

SPA可以工作得很好,但它們也可能成為性能瓶頸。所以,讓我們試著把它們變得更好。

到此這篇關(guān)于如何優(yōu)化 Vue.js 應(yīng)用程序的文章就介紹到這了,更多相關(guān)Vue.js 應(yīng)用程序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論