iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0
JavaScript

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

Day 7:進階模組化設計之 ThemeProvider 應用

  • 分享至 

  • xImage
  •  

在上一篇文章中,我們介紹了如何使用模組化設計來簡化應用中的主題切換和語言切換功能。通過將按鈕元件(ThemeButtonLangButton )分離,我們成功減輕了 App.js 的負擔,使程式碼更具可讀性和可維護性。然而,隨著應用規模的不斷擴大,僅僅使用這種基礎的模組化設計仍然不足以有效管理整個應用的狀態。

在本篇文章中,我們將進一步優化這個設計,通過引入 ThemeProviderContext API,並結合 自定義 Hook ,實現更靈活、更易擴展的模組化設計。

基礎模組化設計的局限性

隨著應用程式的規模擴大,基礎模組化設計會逐漸暴露出一些問題:

  1. 狀態傳遞的複雜性:當多個元件需要共享狀態時,必須通過 props 層層傳遞,例如 isDarkModetoggleMode。隨著應用層級的增多,這樣的傳遞方式會讓代碼變得繁瑣、不易維護,尤其是在多層級的元件間傳遞時,狀態的來源和流向變得難以追蹤。
  2. 狀態集中管理的困難:當各個元件各自管理狀態時,這會導致全局狀態難以統一處理。當應用擴展或需求改變時,往往需要跨越多個文件或元件進行修改,這不僅增大了維護負擔,也容易出現錯誤。

這時候,你是否開始思考:有沒有更好的方式來集中管理狀態,同時減少元件之間不必要的依賴?

答案就是 自定義 Hook

自定義 Hook 是 React 中的一個強大工具,它能幫助我們將重複的狀態邏輯抽取出來,封裝成一個可重用的函數,讓多個元件共享這些邏輯,從而減少繁瑣的 props 傳遞。透過自定義 Hook,我們可以集中管理狀態邏輯,讓元件更加專注於自身的業務邏輯,無需反覆處理共享狀態的問題。這使程式碼更靈活、可維護,也提升了程式碼的重用性。

接下來,我們將通過具體程式碼示例,演示如何使用自定義 Hook 來管理應用中的主題狀態,並解決這些挑戰。

Day07

實際演練:ThemeProvider 和 自定義 Hook

Step 1: 創建 ThemeContext

首先,我們需要一個 ThemeContext 來管理應用中的主題狀態,這樣就能讓所有組件方便地訪問或修改主題(例如深色模式或淺色模式)。為此,我們使用 React 的 Context API,並設計了一個自定義 Hook useTheme 來簡化這個過程。

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

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

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

    const toggleTheme = () => {
        setIsDarkMode(prevMode => !prevMode);
    };

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

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

詳解:

  1. ThemeContext:這是一個上下文,用來存儲主題狀態和切換主題的功能。
  2. ThemeProvider:這個組件負責為整個應用提供主題的狀態管理,它包含了一個 toggleTheme 函數,允許在深色模式和淺色模式之間切換。任何包裹在 ThemeProvider 內的子組件都可以透過上下文訪問主題狀態。
  3. useTheme:這是一個自定義 Hook,讓其他組件可以輕鬆地獲取和操作主題狀態,而不必重複寫上下文的邏輯。

這樣設計的好處是,通過 useTheme,我們可以方便地在應用中的任何地方讀取或修改主題狀態,從而實現高效的代碼重用。

步驟 2: 將 ThemeProvider 引入index.js

接下來,我們需要在應用程式的根文件 index.js 中使用 ThemeProvider,使整個應用中的所有元件都可以訪問主題狀態。

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from '@/utils/ThemeContext';

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

步驟 3: 將 ThemeProvider 引入index.js

接下來,我們可以在 App.js 中根據 ThemeProvider 提供的 isDarkMode 狀態來動態應用不同的主題樣式。在這裡,我們使用了 i18nextuseTranslation Hook 來管理應用的多語言切換,如果你不熟悉 i18n 的整合,請參考我們之前的文章 Day 5:整合 i18n,打造多語言 React 應用

import LangButton from '@/components/LangButton';
import Logo from '@/components/Logo';
import { useTheme } from '@/utils/ThemeContext';
import * as styles from "@/APP.module.scss";
import '@/styles/_language.scss';

const App = () => {

    // 使用 useTranslation 取得翻譯函數 t
    const { t } = useTranslation();
    const { isDarkMode, toggleTheme } = useTheme();

    return (
        <div className={`${styles.appContainer}
            ${isDarkMode ? styles.darkMode : styles.lightMode}
            ${i18n.language}
            `}>
            <Logo />
            <h1>{t('welcome')}</h1>
            <p>{t('description')}</p>
            <div>
                <ThemeButton />
                <LangButton />
            </div>
        </div >
    );
};

export default App;

這樣設計可以根據主題狀態自動切換樣式,並保持 ThemeProvider 和 UI 元件的解耦。

Step 4: 更新元件 ThemeButton

ThemeButton 中,我們使用 useTheme 來訪問主題切換邏輯,這樣不需要通過 props 傳遞狀態,元件間的依賴性大幅降低。

import React from 'react';
import * as styles from './ThemeButton.module.scss';
import { useTheme } from '@/utils/ThemeContext';
import { useTranslation } from 'react-i18next';

const ThemeButton = () => {
    const { isDarkMode, toggleTheme } = useTheme();
    const { t } = useTranslation();  // 使用 useTranslation Hook 獲取翻譯函數
    return (
        <button
            className={isDarkMode ? styles.dark_theme_btn : styles.light_theme_btn}
            onClick={toggleTheme}>
            {isDarkMode ? t('switch_to_light') : t('switch_to_dark')}
        </button>
    );
}
export default ThemeButton;

現在,ThemeButton 不再需要從 App.js 接收任何 props。通過使用 ueTheme,它可以直接訪問主題狀態和切換功能,這大大簡化了組件之間的依賴關係。

Step 5: 練習其他元件 LangButtonLogo

現在,您可以嘗試自己練習,將 LangButtonLogo 這些元件進行一些小幅修改,讓它們也能通過 useTheme 訪問主題狀態,而不再依賴從 App.js 傳遞的 props。這樣做不僅能減少元件之間的耦合,還能提高程式碼的可讀性和可維護性。

具體來說,您可以在 LangButton 中使用 useTheme 來替代傳遞prop的 isDarkMode,這樣可以輕鬆訪問主題狀態,實現動態樣式的切換。

這些改動僅限於狀態管理部分,元件的核心邏輯無需改變。您可以基於步驟4進行這些優化,從而提升您的程式碼品質。

結語

通過這篇文章,我們探討了如何利用 ThemeProviderContext API 來集中管理應用狀態,並結合自定義 Hook useTheme,實現了狀態邏輯的簡化和重用。這樣的設計減少了 props 層層傳遞的繁瑣過程,讓元件更加專注於自身的業務邏輯,從而提升了代碼的靈活性和可維護性。

但當應用規模進一步擴大,你是否會遇到更多挑戰?在 Context API 中,狀態變更的頻率會否導致不必要的重繪?是否需要使用 useMemouseCallback 來提升性能?暫時停下來,思考一下這些問題。這些都是你在未來開發中可能會遇到的關鍵點。

在下一篇文章中,我們將深入探討這些問題,並提供具體的解決方案,幫助你更有效地管理多個上下文並優化應用性能。

此外,部分修正內容尚未完全在文章中展示,完整的代碼實作及進一步的練習題已上傳至 GitHub。歡迎大家前往查看,並嘗試自行完成 LangButton 、Log等元件的進階模組化演練。

👉 前往 GitHub 的 v0.7.0-theme-context-hooks查看完整代碼


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



上一篇
Day 6:基礎模組化設計優化 React 應用
下一篇
Day 8 :優化 Context 重繪,提升 React 性能
系列文
從PM到前端開發:我的React作品集之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言