在上一篇文章中,我們介紹了如何通過 SASS 和 react-i18next
結合,實現 React 應用的主題和語言切換功能,讓用戶可以自由切換語言和主題風格。然而,隨著功能的不斷增多,我們發現 App.js
的邏輯變得愈發複雜且難以維護。這給我們的開發和擴展帶來了極大的挑戰。
因此,本篇文章將介紹如何運用基礎模組化設計,將主題切換和語言切換功能拆分為獨立的元件,解決按鈕無法跟隨主題變更的問題,從而改善應用的維護性和擴展性。這是一個循序漸進的過程,適合正在學習模組化設計的開發者進行練習。此外,我們會將 Logo 的元件實作作為讀者練習的一部分,鼓勵大家自行設計和實現。
基礎模組化設計是將應用邏輯拆分成獨立的組件(components),使每個組件專注於自己的功能。這樣的設計使應用更加清晰、易於維護和擴展,尤其適合管理日益複雜的應用邏輯。
隨著應用邏輯的增長,像 App.js
這樣的核心元件會逐漸承擔過多的責任,包括狀態管理和各種功能邏輯。這會導致以下問題:
因此,我們將把應用中的主要功能(例如主題切換和語言切換)從 App.js
中拆分出來,並封裝成獨立的組件。這不僅能讓程式碼結構更清晰,還有助於未來的功能擴展。基礎模組化設計的主要目標是通過職責分離,減少耦合,讓每個組件更加專注和易於管理。
在本篇文章中,我們將進一步拆分主題切換(ThemeButton)、語言切換(LangButton)。通過這種模組化設計,應用變得更加靈活,且日後的擴展與維護成本也會大幅降低。以下是預計本次修改或新增的檔案,並展示這些變動如何提升應用的模組化設計。如果對專案結構不熟悉,建議先參考 [Day 1:用 Webpack 初始化 Day 1:用 Webpack 初始化 React 專案
react-webpack-starter/
├── src/ # React 原始碼
│ ├── components/ # React 組件
│ │ ├── LangButton.jsx # 語言切換按鈕
│ │ ├── LangButton.module.scss # 語言切換按鈕樣式
│ │ ├── Logo.jsx # 應用 Logo
│ │ ├── Logo.module.scss # Logo 樣式
│ │ ├── ThemeButton.jsx # 主題切換按鈕
│ │ └── ThemeButton.module.scss # 主題切換按鈕樣式
│ ├── styles/ # 全域樣式
│ │ ├── _button.scss # 按鈕樣式
│ │ └── _theme.scss # 主題樣式
│ ├── utils/ # 工具函數和輔助功能
│ │ └── i18n.js # 語言配置文件
│ ├── App.js # 主應用組件
│ ├── APP.module.scss # 主應用樣式模組
通過這種模組化設計,邏輯被劃分到各自的元件,大大減輕了 App.js
的負擔,使應用結構更加清晰且易於維護。接下來,我們將詳細討論各元件的實作細節。
首先,我們會在 src/styles/
資料夾下創建一個 _button.scss
檔案,統一管理按鈕的共用樣式,使得樣式能夠在不同元件中共用。
// _button.scss
@mixin button-styles($bg-color, $text-color) {
padding: 10px 20px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
background-color: $bg-color;
color: $text-color;
&:hover {
background-color: darken($bg-color, 10%);
}
}
這個 mixin
可以靈活地應用在任何按鈕中,確保樣式一致。通過傳入兩個的顏色參數$bg-color
和 $text-color
,可以定義不同主題的按鈕樣式。
接著,在 src/components/
目錄中,建立一個新的元件 ThemeButton.jsx,負責切換應用的主題。
import React from 'react';
import * as styles from './ThemeButton.module.scss';
const ThemeButton = ({ isDarkMode, toggleMode }) => {
return (
<button
className={isDarkMode ? styles.dark_theme_btn : styles.light_theme_btn}
onClick={toggleMode}>
切換到 {isDarkMode ? 'Light Mode' : 'Dark Mode'}
</button>
);
}
export default ThemeButton;
接下來,我們為這個按鈕建立樣式文件 ThemeButton.module.scss
,並引入按鈕共用樣式 _button.scss
和_theme.scss
,確保按鈕的樣式能跟著主題進行切換。
@import '@/styles/button';
@import '@/styles/theme';
.light_theme_btn {
margin: 10px;
@include button-styles($button-primary-background, $button-primary-text)
}
.dark_theme_btn {
margin: 10px;
@include button-styles($button-dark-primary-background, $button-dark-primary-text);
}
語言切換按鈕與主題切換的邏輯類似,但這次我們要處理的是應用的語言環境切換。
import React from 'react'
import i18n from '@/utils/i18n';
import * as styles from './LangButton.module.scss';
const LangButton = ({ isDarkMode }) => {
// 切換語言選項的函數
const toggleLanguage = () => {
const newLang = i18n.language === 'en' ? 'zh' : 'en';
i18n.changeLanguage(newLang);
}
return (
<button
className={isDarkMode ? styles.dark_lang_btn : styles.light_lang_btn}
onClick={toggleLanguage}>
🌐 {i18n.language.toUpperCase()} ▼
</button>
)
}
export default LangButton
接著,為語言切換按鈕建立樣式文件 LangButton.module.scss,並使用之前定義的共用樣式:
@import '@/styles/button';
@import '@/styles/theme';
.light_lang_btn {
margin: 10px;
@include button-styles($button-primary-background, $button-primary-text)
}
.dark_lang_btn {
margin: 10px;
@include button-styles($button-dark-primary-background, $button-dark-primary-text);
}
在本次練習中,我們已經展示了如何模組化主題切換按鈕和語言切換按鈕的功能。現在,作為進一步的練習,我們將 Logo 的實作留給讀者自行完成。你可以嘗試創建一個 Logo 元件,並將其樣式與應用分離,實現模組化設計。
這將是一個很好的機會來應用你學到的知識,並進一步加深你對模組化設計的理解。可以參考前面按鈕的步驟,為 Logo 元件設計自己的樣式和邏輯
最後,在 App.js
中使用別名方式引入 ThemeButton、LangButton 和你自行實作的 Logo。這樣可以進一步減少 App.js
的程式碼複雜度。
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import i18n from '@/utils/i18n';
import ThemeButton from '@/components/ThemeButton';
import LangButton from '@/components/LangButton';
import Logo from '@/components/Logo';
import * as styles from "@/APP.module.scss";
import '@/styles/_language.scss';
const App = () => {
// 使用 useTranslation 取得翻譯函數 t
const { t } = useTranslation();
const [isDarkMode, setIsDarkMode] = useState(false);
const toggleMode = () => {
setIsDarkMode(!isDarkMode);
};
return (
<div className={`${styles.appContainer}
${isDarkMode ? styles.darkMode : styles.lightMode}
${i18n.language}
`}>
<Logo isDarkMode={isDarkMode} />
<h1>{t('welcome')}</h1>
<p>{t('description')}</p>
<div>
<ThemeButton isDarkMode={isDarkMode} toggleMode={toggleMode} />
<LangButton isDarkMode={isDarkMode} />
</div>
</div >
);
};
export default App;
這樣,原本 46 行的程式碼已經縮減到 34 行,結構更為清晰,且更易於維護。
在這篇文章中,我們展示了如何將 React 應用中的主題切換和語言切換功能進行模組化設計。通過將這些邏輯拆分成獨立的元件並使用 SASS Mixin
共用樣式,我們成功減輕了 App.js
的負擔,使代碼結構更加清晰、易於維護和擴展。
你現在已經掌握了模組化設計的基礎,這讓你的應用代碼變得更清晰、易於管理。但隨著應用規模的擴大,僅僅依靠基礎的模組化設計可能不足以應對更複雜的功能需求。比如,當你的應用需要頻繁地在不同元件間共享狀態時,將邏輯封裝到 Context
這樣的進階工具中,能幫助你更高效地管理狀態,保持代碼的整潔和靈活性。
在下一篇文章中,我們將討論如何將主題切換邏輯封裝到類似 ThemeProvider
的 Context
中,使得主題切換功能變得更加集中和靈活。這將進一步優化代碼結構,並提供一個更高效的方式來管理應用的主題狀態。
你是否感覺模組化設計讓應用的邏輯更加清晰了?接下來,我們將通過使用
ThemeProvider
模式來處理主題切換,進一步提升應用的可維護性和擴展性。
此外,部分修正內容尚未完全在文章中展示,完整的代碼實作及進一步的練習題已上傳至 GitHub。歡迎大家前往查看,並嘗試自行完成 Logo 組件的模組化設計。
👉 前往 GitHub 的 v0.6.0-modularized_components查看完整代碼
✨ 流光館Luma<∕> ✨ 期待與你繼續探索更多技術知識!