在應用規模增長的過程中,性能優化始終是前端工程師需要解決的重要課題。上篇文章中,我們討論了如何使用自訂日誌系統來提升應用效率。而今天,我們將進一步探討通過 Lazy Loading 技術和解決 React.memo 引發的渲染問題,進一步優化 React 應用的加載與渲染性能。
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 技術可以確保字體文件僅在需要時才加載,而不是一次性載入所有文件。
在這裡,我們將使用 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
:這個屬性確保在字體加載期間,瀏覽器會使用默認字體進行替代,一旦字體加載完成,系統會自動切換為正確的字體。這樣即使字體需要一些時間加載,用戶也能看到內容,不會因為字體延遲而顯示空白。這樣的字體加載策略能有效提高網頁的性能,減少不必要的帶寬浪費,同時也能保證用戶體驗,避免字體加載不及時影響頁面呈現。
接下來,在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
以防止不必要的渲染,從而達到性能與功能的平衡。
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>
);
通過這些調整,我們能確保全局樣式(包括字體和語言)在應用中的正確應用,並提升整體的樣式管理和應用一致性。
要確認字體懶加載是否正確實現,可以使用 Chrome DevTools 進行檢查,具體步驟如下:
F12
來打開開發者工具。Network
面板,然後切換語言,觀察網路請求是否在語言切換到中文時才加載字體文件。這樣的檢查可以確保我們的Lazy Loading有效,並且字體文件不會多次重複加載,進一步優化應用的網絡性能。
在本文中,我們展示了如何通過 Lazy Loading 技術動態加載字體等資源,從而提升應用的加載和渲染性能。同時,我們解決了之前因使用 React.memo
導致的按鈕文字無法正確更新的問題。這些優化技術對於應用的性能至關重要,尤其是在應用規模不斷擴大的情況下。
你是否曾經遇到過在單頁應用中管理不同頁面狀態的困難?了解如何使用路由技術更好地管理應用中的頁面切換,提升應用的可維護性與用戶體驗。
在下一篇文章中,我們將基於現有的專案基礎,介紹如何使用 React Router 來實現頁面切換,並深入探討路由管理對於現代單頁應用(SPA)的重要性。這將是為期約 20 天的實作過程,因為專案的頁面將變得更加複雜。我們將逐步實作多個頁面,涵蓋 UI 設計、狀態管理、API 整合等各個方面,最終完成一個完整的應用
此外,我們已將完整的代碼實作與更多練習題上傳至 GitHub,鼓勵大家前往查看,並回顧文章中的概念,挑戰更進階的優化練習。
👉 前往 GitHub 的 v0.10.0-lazy-loading 查看完整程式碼
✨ 流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!