在上一篇文章中,我們介紹了如何使用模組化設計來簡化應用中的主題切換和語言切換功能。通過將按鈕元件(ThemeButton
和 LangButton
)分離,我們成功減輕了 App.js
的負擔,使程式碼更具可讀性和可維護性。然而,隨著應用規模的不斷擴大,僅僅使用這種基礎的模組化設計仍然不足以有效管理整個應用的狀態。
在本篇文章中,我們將進一步優化這個設計,通過引入 ThemeProvider
和 Context API
,並結合 自定義 Hook ,實現更靈活、更易擴展的模組化設計。
隨著應用程式的規模擴大,基礎模組化設計會逐漸暴露出一些問題:
props
層層傳遞,例如 isDarkMode
和 toggleMode
。隨著應用層級的增多,這樣的傳遞方式會讓代碼變得繁瑣、不易維護,尤其是在多層級的元件間傳遞時,狀態的來源和流向變得難以追蹤。這時候,你是否開始思考:有沒有更好的方式來集中管理狀態,同時減少元件之間不必要的依賴?
答案就是 自定義 Hook。
自定義 Hook 是 React 中的一個強大工具,它能幫助我們將重複的狀態邏輯抽取出來,封裝成一個可重用的函數,讓多個元件共享這些邏輯,從而減少繁瑣的 props
傳遞。透過自定義 Hook,我們可以集中管理狀態邏輯,讓元件更加專注於自身的業務邏輯,無需反覆處理共享狀態的問題。這使程式碼更靈活、可維護,也提升了程式碼的重用性。
接下來,我們將通過具體程式碼示例,演示如何使用自定義 Hook 來管理應用中的主題狀態,並解決這些挑戰。
ThemeProvider
和 自定義 HookThemeContext
首先,我們需要一個 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);
詳解:
ThemeContext
:這是一個上下文,用來存儲主題狀態和切換主題的功能。ThemeProvider
:這個組件負責為整個應用提供主題的狀態管理,它包含了一個 toggleTheme
函數,允許在深色模式和淺色模式之間切換。任何包裹在 ThemeProvider
內的子組件都可以透過上下文訪問主題狀態。useTheme
:這是一個自定義 Hook,讓其他組件可以輕鬆地獲取和操作主題狀態,而不必重複寫上下文的邏輯。這樣設計的好處是,通過 useTheme
,我們可以方便地在應用中的任何地方讀取或修改主題狀態,從而實現高效的代碼重用。
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>
);
ThemeProvider
引入index.js
接下來,我們可以在 App.js
中根據 ThemeProvider
提供的 isDarkMode
狀態來動態應用不同的主題樣式。在這裡,我們使用了 i18next
的 useTranslation 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 元件的解耦。
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
,它可以直接訪問主題狀態和切換功能,這大大簡化了組件之間的依賴關係。
LangButton
、Logo
現在,您可以嘗試自己練習,將 LangButton
和 Logo
這些元件進行一些小幅修改,讓它們也能通過 useTheme
訪問主題狀態,而不再依賴從 App.js
傳遞的 props。這樣做不僅能減少元件之間的耦合,還能提高程式碼的可讀性和可維護性。
具體來說,您可以在 LangButton
中使用 useTheme
來替代傳遞prop的 isDarkMode
,這樣可以輕鬆訪問主題狀態,實現動態樣式的切換。
這些改動僅限於狀態管理部分,元件的核心邏輯無需改變。您可以基於步驟4進行這些優化,從而提升您的程式碼品質。
通過這篇文章,我們探討了如何利用 ThemeProvider
和 Context API
來集中管理應用狀態,並結合自定義 Hook useTheme
,實現了狀態邏輯的簡化和重用。這樣的設計減少了 props
層層傳遞的繁瑣過程,讓元件更加專注於自身的業務邏輯,從而提升了代碼的靈活性和可維護性。
但當應用規模進一步擴大,你是否會遇到更多挑戰?在 Context API 中,狀態變更的頻率會否導致不必要的重繪?是否需要使用
useMemo
或useCallback
來提升性能?暫時停下來,思考一下這些問題。這些都是你在未來開發中可能會遇到的關鍵點。
在下一篇文章中,我們將深入探討這些問題,並提供具體的解決方案,幫助你更有效地管理多個上下文並優化應用性能。
此外,部分修正內容尚未完全在文章中展示,完整的代碼實作及進一步的練習題已上傳至 GitHub。歡迎大家前往查看,並嘗試自行完成 LangButton 、Log等元件的進階模組化演練。
👉 前往 GitHub 的 v0.7.0-theme-context-hooks查看完整代碼
✨ 流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!