iT邦幫忙

2024 iThome 鐵人賽

DAY 10
0
JavaScript

從PM到前端開發:我的React作品集之旅系列 第 10

Day 10 : Lazy Loading 優化 React 大文件加載

  • 分享至 

  • xImage
  •  

在應用規模增長的過程中,性能優化始終是前端工程師需要解決的重要課題。上篇文章中,我們討論了如何使用自訂日誌系統來提升應用效率。而今天,我們將進一步探討通過 Lazy Loading 技術和解決 React.memo 引發的渲染問題,進一步優化 React 應用的加載與渲染性能。

Lazy Loading 的必要性

Lazy Loading 是一種在需要時才加載資源的技術。隨著應用變得更加複雜,資源體積也會增加,如果在單頁應用(SPA)中一次性加載所有資源,會導致用戶在初次訪問時等待過久,從而影響體驗。Lazy Loading 通過僅在真正需要時才加載資源(如圖片、字體、第三方庫),有效解決了這一問題,減少初次加載的資源量,進而提升頁面加載速度。

例如,當應用中包含較大的文件(如字體文件)時,這些文件會在應用初始化時一次性加載,導致加載時間過長。透過懶加載,我們可以根據需求動態加載這些文件,從而提高初次加載的性能。

在本專案中,當你執行 npm run build 之後,你會在命令行中看到這樣的 Webpack 警告:

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
fonts/jf-openhuninn.woff2 (2.87 MiB)

這樣的警告表明字體文件過大,會拖慢應用的加載速度。Lazy Loading 技術可以確保字體文件僅在需要時才加載,而不是一次性載入所有文件。

實際演練

Step 1: 實現中文字體 Lazy Loading

在這裡,我們將使用 document.fonts.load() 來檢查字體是否已經加載,並使用動態 import() 來進行懶加載。如果字體已經加載過,就不需要再次加載。這樣,我們的字體文件就只會在需要時加載,而不是一開始就浪費帶寬去下載這個大文件。

//src/utils/loadChineseFont.js
import { log, logLevel } from '@/utils/log';

const loadChineseFont = async () => {
    const isLoaded = await document.fonts.load('1em Open Huninn');

    if (isLoaded.length > 0) {
        log(logLevel.INFO, 'Chinese font already loaded.');
        return;
    }
    try {
        const fontPath = await import('@/assets/fonts/open-huninn-font/jf-openhuninn.woff2');

        const font = new FontFace('Open Huninn', `url(${fontPath.default})`, {
            weight: 'normal',
            style: 'normal',
            display: 'swap',
        });
        log(logLevel.DEBUG, fontPath.default);
        await font.load();
        document.fonts.add(font);
        log(logLevel.INFO, 'Chinese font loaded successfully.');
    } catch (error) {
        log(logLevel.ERROR, 'Error loadChineseFont:', error);
    }
};

export default loadChineseFont;

詳解:

  • document.fonts.load():用來檢查字體是否已經加載。如果字體已經加載過,則不再重複加載,主要 防止字體的二次加載,提升應用性能。
  • 動態 import():只在需要時才加載字體文件,並結合 document.fonts.load() 確保字體只加載一次。這樣可以避免重複下載大文件,提高頁面加載速度。
  • log():我們使用自訂的日誌系統來記錄字體加載過程中的關鍵步驟,這對開發中的調試與監控非常有幫助。請參考我們之前的文章 Day 9:精準控制日誌提升 React 效率,以獲取更多關於日誌系統的資訊。
  • font-display: swap:這個屬性確保在字體加載期間,瀏覽器會使用默認字體進行替代,一旦字體加載完成,系統會自動切換為正確的字體。這樣即使字體需要一些時間加載,用戶也能看到內容,不會因為字體延遲而顯示空白。

這樣的字體加載策略能有效提高網頁的性能,減少不必要的帶寬浪費,同時也能保證用戶體驗,避免字體加載不及時影響頁面呈現。

Step 2: 在語言切換時加載字體

接下來,在LangButton 元件中根據語言切換動態加載字體:

import React, { useState } from 'react'
import i18n from '@/utils/i18n';
import * as styles from './LangButton.module.scss';
import { useTheme } from '@/utils/ThemeContext';
import { log, logLevel } from '@/utils/log';
import loadChineseFont from '@/utils/loadChineseFont';

const LangButton = ({ }) => {
    // 切換語言選項的函數
    const { isDarkMode } = useTheme();
    const [currentLanguage, setCurrentLanguage] = useState(i18n.language);

    const toggleLanguage = () => {
        const newLang = i18n.language === 'en' ? 'zh' : 'en';
        if (newLang === 'zh') {
            loadChineseFont();
        }
        i18n.changeLanguage(newLang);
        setCurrentLanguage(newLang);  // 更新語言狀態,觸發重新渲染
    }

    log(logLevel.DEBUG, 'LangButton rendered');

    return (
        <button
            className={isDarkMode ? styles.dark_lang_btn : styles.light_lang_btn}
            onClick={toggleLanguage}>
            🌐 {i18n.language.toUpperCase()} ▼
        </button>
    )
}

export default React.memo(LangButton);

在之前的實作中,我們在元件 LangButton 中使用了 React.memo 來優化渲染性能,但因為 i18n.language.toUpperCase() 並不屬於傳遞參數 props,按鈕的文字無法隨語言變化即時更新。通過 useState 來管理當前語言的狀態,我們可以確保每次語言切換後按鈕文字正確更新,同時保留 React.memo 以防止不必要的渲染,從而達到性能與功能的平衡。

Step 3 :index.js 中引入 _global.scss

在之前的實作中,由於全局樣式的處理存在問題,導致字體和語言樣式未能正確應用。在這裡,我們通過新增並引入 _global.scss,確保樣式能夠正確生效,尤其是字體和語言的相關樣式。

_global.scss 文件的內容如下:

//styles/_global.scss
@import '@/styles/theme';
@import '@/styles/language';

這樣可以確保應用的主題和語言樣式能夠在整個應用中正確應用。

接著,在 index.js 中引入這個全局樣式文件,並確保 i18n 在應用渲染之前初始化好。

import React from 'react';
import ReactDOM from 'react-dom/client';
import '@/utils/i18n'; // 确保 i18n 初始化在应用渲染之前
import App from './App';
import { ThemeProvider } from '@/utils/ThemeContext';
import '@/styles/_global.scss' // 引入全局樣式

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <ThemeProvider>
        <App />
    </ThemeProvider>
);

通過這些調整,我們能確保全局樣式(包括字體和語言)在應用中的正確應用,並提升整體的樣式管理和應用一致性。

Step 4 確認字體是否正確Lazy Loading

要確認字體懶加載是否正確實現,可以使用 Chrome DevTools 進行檢查,具體步驟如下:

  1. 打開 Chrome,並進入你的應用,按 F12 來打開開發者工具。
  2. 進入 Network 面板,然後切換語言,觀察網路請求是否在語言切換到中文時才加載字體文件。
  3. 確保字體文件不會在應用初始化時被提前加載,而是在切換到中文時動態加載。
  4. 再次切換回英文並切換回中文,確認字體文件只會加載一次,後續切換語言不會重複下載字體。

這樣的檢查可以確保我們的Lazy Loading有效,並且字體文件不會多次重複加載,進一步優化應用的網絡性能。

Day10

結語

在本文中,我們展示了如何通過 Lazy Loading 技術動態加載字體等資源,從而提升應用的加載和渲染性能。同時,我們解決了之前因使用 React.memo 導致的按鈕文字無法正確更新的問題。這些優化技術對於應用的性能至關重要,尤其是在應用規模不斷擴大的情況下。

你是否曾經遇到過在單頁應用中管理不同頁面狀態的困難?了解如何使用路由技術更好地管理應用中的頁面切換,提升應用的可維護性與用戶體驗。

在下一篇文章中,我們將基於現有的專案基礎,介紹如何使用 React Router 來實現頁面切換,並深入探討路由管理對於現代單頁應用(SPA)的重要性。這將是為期約 20 天的實作過程,因為專案的頁面將變得更加複雜。我們將逐步實作多個頁面,涵蓋 UI 設計、狀態管理、API 整合等各個方面,最終完成一個完整的應用

此外,我們已將完整的代碼實作與更多練習題上傳至 GitHub,鼓勵大家前往查看,並回顧文章中的概念,挑戰更進階的優化練習。
👉 前往 GitHub 的 v0.10.0-lazy-loading 查看完整程式碼


流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!



上一篇
Day 9 : 精準控制日誌提升 React 效率
下一篇
Day 11: 用 React Router 實現頁面導航
系列文
從PM到前端開發:我的React作品集之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言