跳至主要内容

React Patterns

Compound Pattern

在一個共同的父元件中管理多個子元件狀態的模式

使用時機

  • 相關聯的 UI 元件:適用於相關聯的 UI 元件,例如,表單元件可能包含標籤、輸入框、按鈕等相關元件,它們一起構成一個完整的表單。
  • 共享狀態: 允許這些元件共享父元件中的狀態。
  • 更好的封裝: 可以實現更好的封裝,因為元件的實現細節被隱藏在父元件內部,子元件只需關心與之相關的部分。

Pros

  1. 可以在多個相關的元件中共享狀態
  2. 不需要明確載入 child component,只需要載入主元件後就能使用
const FlyOutContext = createContext();

// Compound Component 的父元件
function FlyOut(props) {
const [open, toggle] = useState(false);

return (
<FlyOutContext.Provider value={{ open, toggle }}>
{props.children}
</FlyOutContext.Provider>
);
}

// Compound Component 的子元件
function Toggle() {
const { open, toggle } = useContext(FlyOutContext);

return (
<div onClick={() => toggle(!open)}>
<Icon />
</div>
);
}

// Compound Component 的子元件
function List({ children }) {
const { open } = useContext(FlyOutContext);
return open && <ul>{children}</ul>;
}

// Compound Component 的子元件
function Item({ children }) {
return <li>{children}</li>;
}

// Compound Component Pattern 的關鍵
FlyOut.Toggle = Toggle;
FlyOut.List = List;
FlyOut.Item = Item;

export default function App() {
return (
<FlyOut>
<FlyOut.Toggle />
<FlyOut.List>
<FlyOut.Item>Edit</FlyOut.Item>
<FlyOut.Item>Delete</FlyOut.Item>
</FlyOut.List>
</FlyOut>
);
}

HOC Pattern (Higher Order Component Pattern)

Higher Order Component 可以把 Component 當作參數傳入,並且回傳一個「增強版」的 Component

使用時機

  • 程式複用: 當你有多個組件需要相同的邏輯
  • 抽象通用邏輯: 如果你有一些通用的邏輯,比如處理驗證、權限控制、數據加載等,可以使用 HOC 將這些邏輯抽象出來,使組件更加專注於其本身的業務邏輯
  • 屬性的操作或注入: 有時你可能需要在一個或多個組件中操作或注入屬性,HOCs 可以幫助你在不修改原始組件的情況下實現這些操作。

Pros

  1. 程式複用: HOC 允許你將通用邏輯抽象出來,使其能夠在多個地方重複使用,減少程式重複
  2. 組件邏輯分離: 通過將特定功能包裝在 HOC 中,你可以使組件保持相對簡單和專注於其主要功能
  3. 動態配置: HOC 允許你根據需要動態配置組件的功能,使組件更加靈活

Cons

  1. 嵌套地獄: 過多的 HOC 嵌套可能導致程式難以理解和維護
  2. 屬性衝突: 如果多個 HOC 依賴於相同的屬性名稱,可能會導致衝突
  3. 不透明性: HOC 可能導致組件層次結構變得不透明,對於新開發者或維護者來說可能難以理解
import React, { useState, useEffect } from "react";

// Higher Order Component for User Authentication
const withAuth = (WrappedComponent) => {
return (props) => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

useEffect(() => {
const authenticateUser = async () => {
/* Logic to check if the user is authenticated */
const isAuthenticated = true;
setIsAuthenticated(isAuthenticated);
};

authenticateUser();
}, []);

// Pass the authentication status as a prop to the wrapped component
return <WrappedComponent {...props} isAuthenticated={isAuthenticated} />;
};
};

// Example usage of the withAuth HOC
const MyComponent = ({ isAuthenticated }) => {
return (
<div>
{isAuthenticated ? (
<p>User is authenticated. Render sensitive content here.</p>
) : (
<p>User is not authenticated. Redirect or show a login form.</p>
)}
</div>
);
};

// Wrap MyComponent with the withAuth HOC
const MyComponentWithAuth = withAuth(MyComponent);

Container / Presentation Pattern

用於組織和分離畫面邏輯和業務邏輯的設計模式

  • Container Components: 負責畫面的渲染
  • Presentation Components: 負責狀態管理、資料處理、事件處理等邏輯
  • 多數場景可用 Hooks Pattern 替代

Pros

  1. 分離關注點:有助於分離關注點,使程式碼更容易理解和維護
  2. 可重用性: presentation components 通常是無狀態的,更容易被重用
  3. 測試容易: presentation components 通常只處理 UI 部分,不包含狀態邏輯,因此更容易進行單元測試。

Cons

  1. 檔案複雜:可能導致檔案數量的增加,因為每個 container 都需要相應的 presentation components。使專案的結構變得複雜
  2. 過於細分: 在小型應用中可能會顯得過於細分,增加了程式的冗餘性
import React, { useState } from "react";

// Container Component
const CounterContainer = () => {
const [count, setCount] = useState(0);

const increment = () => {
setCount(count + 1);
};

const decrement = () => {
setCount(count - 1);
};

return (
<CounterPresentation
count={count}
increment={increment}
decrement={decrement}
/>
);
};

// Presentation Component
const CounterPresentation = ({ count, increment, decrement }) => {
return (
<div>
<h2>Counter</h2>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
);
};

Render Props Pattern

元件通過一個函數 prop 將其內部的狀態或邏輯傳遞給另一個元件,這個 prop 通常命名為 render

const Input = (props) => {
const [name, setName] = useState("");

return (
<>
<input value={name} onChange={(e) => setName(e.target.value)} />
// 透過 props.children() 將 name 傳遞給其他元件
{props.children(name)}
</>
);
};

const ReadName = ({ name }) => {
return <h1>{name}</h1>;
};

const Greet = ({ name }) => {
return <h1>Hello {name}</h1>;
};

function App() {
return (
<>
<Input>
{(name) => (
<>
<ReadName name={name} />
<Greet name={name} />
</>
)}
</Input>
</>
);
}

參考資料