iT邦幫忙

2024 iThome 鐵人賽

DAY 8
0
JavaScript

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

Day 8 :優化 Context 重繪,提升 React 性能

  • 分享至 

  • xImage
  •  

在前一篇文章中,我們介紹了如何利用 ThemeProvider 和自定義 Hook useTheme 來集中管理應用的主題狀態。隨著應用規模的擴大,我們開始管理更多的狀態,例如語言切換和用戶設置等。雖然 Context API 是一個集中管理狀態的強大工具,但當不同的上下文被頻繁使用時,可能會面臨性能問題:不必要的元件重繪

在本篇文章中,我們將探討這些重繪問題是如何發生的,並通過一些優化技巧來避免不必要的性能浪費。

如何檢查和優化不必要的重繪

React 的核心特性之一是其“按需渲染”機制,當狀態改變時,只會重新渲染那些受影響的元件。然而,當我們使用 Context API 來集中管理應用狀態時,任何依賴該 Context 的元件都會在 Context 狀態變更時重新渲染,無論它們是否需要更新。例如,假設我們的應用使用 ThemeContext 來管理 isDarkModetoggleTheme,當用戶切換語言時,即使主題狀態(isDarkMode)沒有改變,所有依賴 ThemeContext 的組件仍可能會被觸發重繪,導致性能浪費。

為了確定哪些組件發生了不必要的重繪,我們可以在元件內部加入 console.log 語句,觀察每次渲染的行為。例如,對於 App 元件,我們可以這樣寫:

 if (process.env.NODE_ENV !== 'production') {
        console.log('App rendered');
 } 

同樣地,我們可以在 LogoThemeButtonLangButton 等組件中加入這些日誌,當語言切換時,觀察哪些組件被重繪。如下圖,打開瀏覽器控制台顯示LogoLangButton 仍然發生重繪。

Day08_p

React 提供了幾個實用的工具來減少不必要的重繪,以下是常用的三個:

  • React.memo:高階組件,用於記住元件的輸出,僅當 props 改變時才會觸發重新渲染。
  • useMemo:記住某個值的計算結果,僅在依賴變更時重新計算,避免每次渲染都重新計算不必要的值。
  • useCallback:記住函數的引用,僅在依賴變更時才會生成新的函數,減少不必要的函數創建。

實際演練:使用 React.memouseCallback優化

Step 1:使用 React.memo 優化 Logo

當語言切換時,Logo 不應該重新渲染,因為它與語言狀態無關。我們可以通過 React.memo 來優化:

import React from 'react'
import * as styles from './Logo.module.scss';
import { useTheme } from '@/utils/ThemeContext';

const Logo = ({ }) => {
    // 使用 require 導入圖片
    const logoDark = require('@/assets/logo_dark.png');
    const logoLight = require('@/assets/logo_light.png');

    const { isDarkMode } = useTheme();

    if (process.env.NODE_ENV !== 'production') {
        console.log('Logo rendered');
    }

    return (
        <img src={isDarkMode ? logoDark : logoLight}
            alt="Logo" className={styles.img} />
    )
}

export default React.memo(Logo);

透過 React.memoLogo 元件僅在 isDarkMode 變化時重新渲染,避免因語言切換而重繪。

Step 2: 使用 React.memo 優化 Version 元件

接著,我們新增一個 Version 的元件,其主要功能是顯示應用的版本號。由於這個元件不會受到主題或語言等其他應用狀態的影響,我們可以設計它成為一個完全獨立的元件,避免不必要的重渲染。為了進一步優化,我們使用 React.memo 來包裹這個元件,確保它只有在版本號發生變更時才會重新渲染,而不會因主題或語言的變更被觸發。

我們使用 Webpack 的 DefinePlugin 將版本號從 package.json 中注入到應用的環境變量中。通過這樣的設置,Version 組件可以直接從環境變量中讀取並顯示版本號,無需手動更新。

const webpack = require('webpack');
const packageJson = require('./package.json');

module.exports = {
  //... 其他配置
  plugins: [
    new webpack.DefinePlugin({
      'process.env.REACT_APP_VERSION': JSON.stringify(packageJson.version),
    }),
  ],
};

在這個配置中,我們使用 DefinePlugin 將應用的版本號轉換成一個環境變量,這樣可以在應用中通過 process.env.REACT_APP_VERSION 訪問版本號。

接著,我們在 Version 組件中使用這個變量來顯示版本號,並用 React.memo 來優化渲染。具體代碼如下:

import React from 'react';

const Version = () => {
    const appVersion = process.env.REACT_APP_VERSION;;

    if (process.env.NODE_ENV !== 'production') {
        console.log('Version rendered');
    }

    return (
        <div>
            <p>App Version: {appVersion}</p>
        </div>
    );
};

export default React.memo(Version);

這樣的設計可以確保 Version 組件只在版本號變更時重新渲染,減少不必要的性能浪費。同時,使用環境變量來顯示版本號,讓應用在每次構建時自動更新版本號,簡化了維護工作。

Step 3: 優化 ThemeProvider

ThemeProvider 中,我們可以使用 useCallback 來優化 toggleTheme 函數,確保它的引用只在 isDarkMode 改變時才更新。

// src/utils/ThemeContext.js
import React, { createContext, useState, useContext, useCallback } from 'react';

// 創建一個主題上下文
const ThemeContext = createContext();

// ThemeProvider 組件負責提供主題狀態和切換功能
export const ThemeProvider = ({ children }) => {
    const [isDarkMode, setIsDarkMode] = useState(false);

    // 使用 useCallback 優化 toggleTheme,確保函數只在依賴變更時重新生成
    const toggleTheme = useCallback(() => {
        setIsDarkMode(prevMode => !prevMode);
    }, []);

    return (
        <ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
};

// 自定義 Hook,便於其他組件訪問主題上下文
export const useTheme = () => useContext(ThemeContext);

useCallback 確保 toggleTheme 只有在依賴(isDarkMode)發生變化時才會重新創建,這樣可以避免由於函數重新創建導致的不必要重繪。

結語

通過本文,我們探討了如何使用 useMemouseCallback 來減少不必要的重繪,從而提升 React 應用的性能。這些 Hooks 對於應用中的性能優化非常有幫助,特別是在 ThemeProvider 這樣的場景中,能夠有效地減少由 Context 變更引發的重複渲染。

在完成這些性能優化後,你是否注意到開發過程中的另一個潛在問題:過多的日誌輸出?如何控制日誌的數量與等級,避免在生產環境中出現不必要的日誌?在應用中,是否有一個靈活的方法來根據不同的環境設定日誌輸出級別,以減少運行時的性能影響?

在下一篇文章中,我們將探討如何通過環境變數和日誌等級控制來優化日誌管理。這不僅能夠減少不必要的日誌輸出,還可以確保在開發與生產環境中有針對性的日誌輸出,進一步提升應用的性能和可維護性。

此外,完整的程式碼實作已上傳至 GitHub,歡迎大家前往查看並挑戰更多優化練習。
👉 前往 GitHub 的 v0.8.0-context-render-optimization查看完整代碼


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



上一篇
Day 7:進階模組化設計之 ThemeProvider 應用
下一篇
Day 9 : 精準控制日誌提升 React 效率
系列文
從PM到前端開發:我的React作品集之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言