SOLID 原則是一套軟體設計原則,概念是由 Robert C. Martin 在他的著作《Agile Software Development, Principles, Patterns, and Practices》中提出。雖然最初是為了物件導向程式而設計,但這些原則其實適用於所有軟體設計。前幾天在查設計模式的資料時,發現許多 SOLID 文章寫得超好,特別是針對 React 的指南也很清楚,於是就想好好把文章整理起來~
“A class ought to serve a single, clearly defined purpose, reducing the need for frequent changes.”
雖然上述提到的最小單位是類別,但在 React 中我們可以關注在「一個元件」上,每個元件都有單一的、明確定義的職責,來達到 codebase 的清晰度與可維護性。例如,元件可以負責顯示特定部分、處理使用者輸入或呼叫 API 來取得資料。
以下是在 React 應用程式中實施單一職責原則 (SRP) 的一些指南:
讓你的元件變小,只做一件事:
嘗試將你的元件拆解的更小並且功能單一,並為每個元件分配單一的、明確定義的職責。
不要混合不同的工作:
不要將不相關的任務捆綁到一個元件中。例如,負責顯示表單的元件不應同時處理 API 呼叫以取得清單資料。
使用組合(composition):
透過組合較小的元件來建立可重複使用的 UI 元件。這使得開發者可以將複雜的 UI 分解為更小、更易於管理的部分,這些部分可以輕鬆地在應用程式的不同部分中重複使用。
更聰明地處理 props 和 state:
props 就像將資料和操作傳遞給子組件的信使(父元件傳遞給子元件)。另一方面,state 就像組件的個人記事本,保存其獨特的資訊(元件內部的狀態管理)。建議使用 state 來取得不限於一件的資訊。
在某些情況下,我們不必嚴格遵循 SRP,例如:
表單元件:
表單執行許多工作,例如檢查資料、管理狀態和更新資訊。拆分這些任務可能會讓事情變得混亂,尤其是當我們使用其他工具或函式庫時。
表格元件:
表格也處理不同的任務,例如顯示資料和管理使用者互動方式。將它們分成單獨的部分可能會使我們的程式碼更加混亂。
“Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.”
對擴展開放(open for extension)、對修改封閉(closed for modification)聽起來很抽象,但其實這個原則的意思很簡單:當我們需要為系統添加新功能時,應該透過擴展現有的功能,而不是修改它們。
在 React 中,這就意味著元件應該能夠輕鬆添加新的行為、功能或特性(對擴展開放),但一旦元件建立後,應盡量避免直接改動原始程式碼(對修改封閉),以維持穩定性並減少潛在的錯誤風險。
假設有個顯示使用者詳細資料的元件 <UserProfile>
,然後會根據使用者的「權限狀態」顯示不同的按鈕,如下:
// ❌ 錯誤範例:違反 OCP,因為當權限改變時,會需要修改 <UserProfile> 內的邏輯
const UserProfile = ({ name, isAdmin, onEdit, onView }) => {
return (
<div>
<h1>{name}</h1>
{isAdmin ? (
<button onClick={onEdit}>編輯用戶</button>
) : (
<button onClick={onView}>檢視用戶</button>
)}
</div>
);
};
<UserProfile>
內的邏輯。<UserProfile>
接受 children
prop,以便我們可以透過 Day 10 提到的組合(composition)來擴展行為。分離按鈕邏輯到各自的元件:
// 按鈕元件的確可以用更通用的寫法,不過這邊的範例是語意上的考量
const AdminButton = ({ onEdit }) => (
<button onClick={onEdit}>編輯用戶</button>
);
const GuestButton = ({ onView }) => (
<button onClick={onView}>檢視用戶</button>
);
將 <UserProfile>
改寫為通用容器,並接收 children
:
const UserProfile = ({ name, children }) => {
return (
<div>
<h1>{name}</h1>
{children}
</div>
);
};
在應用中擴展功能,而不修改 <UserProfile>
的邏輯:
const App = ({ isAdmin, onEdit, onView }) => {
return (
<UserProfile name="John Doe">
{isAdmin
? <AdminButton onEdit={onEdit} />
: <GuestButton onView={onView} />
}
</UserProfile>
);
};
這樣的設計方式符合 OCP,因為可以根據不同情況輕鬆擴展,只需新增不同的按鈕元件或其他邏輯,而不必修改 <UserProfile>
的內部程式碼。