iT邦幫忙

2024 iThome 鐵人賽

DAY 11
1
Modern Web

剛入行就一人重新打造公司前端系統?系列 第 11

Day 11 - SOLID 原則在 React 的實踐(一)

  • 分享至 

  • xImage
  •  

SOLID 原則是一套軟體設計原則,概念是由 Robert C. Martin 在他的著作《Agile Software Development, Principles, Patterns, and Practices》中提出。雖然最初是為了物件導向程式而設計,但這些原則其實適用於所有軟體設計。前幾天在查設計模式的資料時,發現許多 SOLID 文章寫得超好,特別是針對 React 的指南也很清楚,於是就想好好把文章整理起來~

SOLID 原則

  1. 單一職責原則 (Single Responsibility Principle, SRP)
  2. 開放封閉原則 (Open/Closed Principle, OCP)
  3. 里氏替換原則 (Liskov Substitution Principle, LSP)
  4. 介面隔離原則 (Interface Segregation Principle, ISP)
  5. 依賴反轉原則 (Dependency Inversion Principle, DIP)

單一職責原則 (Single Responsibility Principle, SRP)

“A class ought to serve a single, clearly defined purpose, reducing the need for frequent changes.”

雖然上述提到的最小單位是類別,但在 React 中我們可以關注在「一個元件」上,每個元件都有單一的、明確定義的職責,來達到 codebase 的清晰度與可維護性。例如,元件可以負責顯示特定部分、處理使用者輸入或呼叫 API 來取得資料。

以下是在 React 應用程式中實施單一職責原則 (SRP) 的一些指南:

  1. 讓你的元件變小,只做一件事:

    嘗試將你的元件拆解的更小並且功能單一,並為每個元件分配單一的、明確定義的職責。

  2. 不要混合不同的工作:

    不要將不相關的任務捆綁到一個元件中。例如,負責顯示表單的元件不應同時處理 API 呼叫以取得清單資料。

  3. 使用組合(composition):

    透過組合較小的元件來建立可重複使用的 UI 元件。這使得開發者可以將複雜的 UI 分解為更小、更易於管理的部分,這些部分可以輕鬆地在應用程式的不同部分中重複使用。

  4. 更聰明地處理 props 和 state:

    props 就像將資料和操作傳遞給子組件的信使(父元件傳遞給子元件)。另一方面,state 就像組件的個人記事本,保存其獨特的資訊(元件內部的狀態管理)。建議使用 state 來取得不限於一件的資訊。

在某些情況下,我們不必嚴格遵循 SRP,例如:

  1. 表單元件:

    表單執行許多工作,例如檢查資料、管理狀態和更新資訊。拆分這些任務可能會讓事情變得混亂,尤其是當我們使用其他工具或函式庫時。

  2. 表格元件:

    表格也處理不同的任務,例如顯示資料和管理使用者互動方式。將它們分成單獨的部分可能會使我們的程式碼更加混亂。

開放封閉原則 Open/Closed Principle (OCP)

“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>
  );
};
  • 這樣的設計違反了 OCP,因為當需求變動時,例如需要新增更多角色或按鈕時,必須直接修改 <UserProfile> 內的邏輯。
  • 為了符合 Open/Closed 原則,我們可以改寫這段程式碼,使其「開放擴展」,但「關閉修改」。作法是將按鈕行為分離到獨立的元件中,並讓 <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> 的內部程式碼。

參考資料


上一篇
Day 10 - Compound Pattern
下一篇
Day 12 - SOLID 原則在 React 的實踐(二)
系列文
剛入行就一人重新打造公司前端系統?27
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言