React Patterns
Compound Pattern
在一個共同的父元件中管理多個子元件狀態的模式
使用時機
- 相關聯的 UI 元件:適用於相關聯的 UI 元件,例如,表單元件可能包含標籤、輸入框、按鈕等相關元件,它們一起構成一個完整的表單。
- 共享狀態: 允許這些元件共享父元件中的狀態。
- 更好的封裝: 可以實現更好的封裝,因為元件的實現細節被隱藏在父元件內部,子元件只需關心與之相關的部分。
Pros
- 可以在多個相關的元件中共享狀態
- 不需要明確載入 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
- 程式複用: HOC 允許你將通用邏輯抽象出來,使其能夠在多個地方重複使用,減少程式重複
- 組件邏輯分離: 通過將特定功能包裝在 HOC 中,你可以使組件保持相對簡單和專注於其主要功能
- 動態配置: HOC 允許你根據需要動態配置組件的功能,使組件更加靈活
Cons
- 嵌套地獄: 過多的 HOC 嵌套可能導致程式難以理解和維護
- 屬性衝突: 如果多個 HOC 依賴於相同的屬性名稱,可能會導致衝突
- 不透明性: 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
- 分離關注點:有助於分離關注點,使程式碼更容易理解和維護
- 可重用性: presentation components 通常是無狀態的,更容易被重用
- 測試容易: presentation components 通常只處理 UI 部分,不包含狀態邏輯,因此更容易進行單元測試。
Cons
- 檔案複雜:可能導致檔案數量的增加,因為每個 container 都需要相應的 presentation components。使專案的結構變得複雜
- 過於細分: 在小型應用中可能會顯得過於細分,增加了程式的冗餘性
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>
</>
);
}