Vue元件懶載入

發表於2023-09-20

在當今快節奏的數字世界中,網站效能對於吸引使用者和取得成功至關重要。然而,對於像首頁這樣的頁面,在不影響功能的前提下最佳化效能就成了一項挑戰。

這就是 Vue 元件懶載入的用武之地。透過將非必要元素的載入推遲到可見時進行,開發人員可以增強使用者體驗,同時確保登陸頁面的快速載入。

懶載入是一種優先載入關鍵內容,同時推遲載入次要元素的技術。這種方法不僅能縮短頁面的初始載入時間,還能節約網路資源,從而使使用者介面更輕量、反應更靈敏。

在本文中,我將向你展示一種簡單的機制,使用 Intersection Observer API 在 Vue 元件可見時對其進行懶載入。

Intersection Observer API

Intersection Observer API 是一種功能強大的工具,它允許開發人員有效地跟蹤和響應瀏覽器視口中元素可見性的變化。

它提供了一種非同步觀察元素與其父元素之間或元素與視口之間交集的方法。它為檢測元素何時可見或隱藏提供了效能優越的最佳化解決方案,減少了對低效滾動事件監聽器的需求,使開發人員能夠在必要時有選擇地載入或操作內容,從而增強使用者體驗。

它通常用於實現諸如無限滾動和圖片懶載入等功能。

非同步元件

Vue 3 提供了 defineAsyncComponent,用於僅在需要時非同步載入元件。

它返回一個元件定義的 Promise:

import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...load component from server
    resolve(/* loaded component */)
  })
})

還可以處理錯誤和載入狀態:

const AsyncComp = defineAsyncComponent({
  // the loader function
  loader: () => import('./Foo.vue'),

  // A component to use while the async component is loading
  loadingComponent: LoadingComponent,
  // Delay before showing the loading component. Default: 200ms.
  delay: 200,

  // A component to use if the load fails
  errorComponent: ErrorComponent,
  // The error component will be displayed if a timeout is
  // provided and exceeded. Default: Infinity.
  timeout: 3000
})

當元件可見時,我們將使用該功能非同步載入元件。

懶載入元件

現在,讓我們結合 Intersection Observer API 和 defineAsyncComponent 函式,在元件可見時非同步載入它們:

import {
  h,
  defineAsyncComponent,
  defineComponent,
  ref,
  onMounted,
  AsyncComponentLoader,
  Component,
} from 'vue';

type ComponentResolver = (component: Component) => void

export const lazyLoadComponentIfVisible = ({
  componentLoader,
  loadingComponent,
  errorComponent,
  delay,
  timeout
}: {
  componentLoader: AsyncComponentLoader;
  loadingComponent: Component;
  errorComponent?: Component;
  delay?: number;
  timeout?: number;
}) => {
  let resolveComponent: ComponentResolver;

  return defineAsyncComponent({
    // the loader function
    loader: () => {
      return new Promise((resolve) => {
        // We assign the resolve function to a variable
        // that we can call later inside the loadingComponent 
        // when the component becomes visible
        resolveComponent = resolve as ComponentResolver;
      });
    },
    // A component to use while the async component is loading
    loadingComponent: defineComponent({
      setup() {
        // We create a ref to the root element of 
        // the loading component
        const elRef = ref();

        async function loadComponent() {
            // `resolveComponent()` receives the
            // the result of the dynamic `import()`
            // that is returned from `componentLoader()`
            const component = await componentLoader()
            resolveComponent(component)
        }

        onMounted(async() => {
          // We immediately load the component if
          // IntersectionObserver is not supported
          if (!('IntersectionObserver' in window)) {
            await loadComponent();
            return;
          }

          const observer = new IntersectionObserver((entries) => {
            if (!entries[0].isIntersecting) {
              return;
            }

            // We cleanup the observer when the 
            // component is not visible anymore
            observer.unobserve(elRef.value);
            await loadComponent();
          });

          // We observe the root of the
          // mounted loading component to detect
          // when it becomes visible
          observer.observe(elRef.value);
        });

        return () => {
          return h('div', { ref: elRef }, loadingComponent);
        };
      },
    }),
    // Delay before showing the loading component. Default: 200ms.
    delay,
    // A component to use if the load fails
    errorComponent,
    // The error component will be displayed if a timeout is
    // provided and exceeded. Default: Infinity.
    timeout,
  });
};

讓我們分解一下上面的程式碼:

我們建立一個 lazyLoadComponentIfVisible 函式,該函式接受以下引數:

  • componentLoader:返回一個解析為元件定義的 Promise 的函式
  • loadingComponent:非同步元件載入時使用的元件。
  • errorComponent:載入失敗時使用的元件。
  • delay:顯示載入元件前的延遲。預設值:200 毫秒。
  • timeout:如果提供了超時時間,則將顯示錯誤元件。預設值:Infinity

函式返回 defineAsyncComponent,其中包含在元件可見時非同步載入元件的邏輯。

主要邏輯發生在 defineAsyncComponent 內部的 loadingComponent 中:

我們使用 defineComponent 建立一個新元件,該元件包含一個渲染函式,用於在傳遞給 lazyLoadComponentIfVisiblediv 中渲染 loadingComponent。該渲染函式包含一個指向載入元件根元素的模板ref

onMounted 中,我們會檢查 IntersectionObserver 是否受支援。如果不支援,我們將立即載入元件。否則,我們將建立一個 IntersectionObserver,用於觀察已載入元件的根元素,以檢測它何時變得可見。當元件變為可見時,我們會清理觀察者並載入元件。

現在,你可以使用該函式在元件可見時對其進行懶載入:

<script setup lang="ts">
import Loading from './components/Loading.vue';
import { lazyLoadComponentIfVisible } from './utils';

const LazyLoaded = lazyLoadComponentIfVisible({
  componentLoader: () => import('./components/HelloWorld.vue'),
  loadingComponent: Loading,
});
</script>

<template>
  <LazyLoaded />
</template>

總結

在本文中,我們學習瞭如何使用 Intersection Observer API 和 defineAsyncComponent 函式在 Vue 元件可見時對其進行懶載入。如果有一個包含許多元件的首頁,並希望改善應用程式的初始載入時間,這將非常有用。

相關文章