← Về danh sách bài họcBài 24/25
🎨 Bài 24: React Patterns - Mẫu Thiết Kế
🎯 Sau bài học này, bạn sẽ:
- Hiểu và áp dụng Compound Components
- Render Props và Higher-Order Components
- Container / Presentational pattern
- Custom Hook pattern (best practice hiện tại)
1. Compound Components
Các components liên quan chia sẻ state ngầm, giống <select> + <option>:
const TabContext = createContext();
function Tabs({ children, defaultTab }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabContext);
return (
<button className={activeTab === value ? 'active' : ''}
onClick={() => setActiveTab(value)}>
{children}
</button>
);
}
function TabPanel({ value, children }) {
const { activeTab } = useContext(TabContext);
return activeTab === value ? <div>{children}</div> : null;
}
// Sử dụng - API rất clean!
<Tabs defaultTab="profile">
<TabList>
<Tab value="profile">Profile</Tab>
<Tab value="settings">Settings</Tab>
</TabList>
<TabPanel value="profile"><Profile /></TabPanel>
<TabPanel value="settings"><Settings /></TabPanel>
</Tabs>2. Render Props
// Component cung cấp logic, consumer quyết định UI
function MouseTracker({ render }) {
const [pos, setPos] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => setPos({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return render(pos);
}
// Sử dụng
<MouseTracker render={({ x, y }) => (
<div>
<p>Mouse: {x}, {y}</p>
<div style={{ position: 'fixed', left: x, top: y }}>🎯</div>
</div>
)} />📌 Ngày nay: Render Props phần lớn được thay thế bởi Custom Hooks, nhưng vẫn hữu ích cho một số thư viện (React Spring, Formik).
3. Container / Presentational
// Presentational: chỉ hiển thị UI, nhận props
function UserListView({ users, loading, onSelect }) {
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map(u => (
<li key={u.id} onClick={() => onSelect(u)}>
{u.name}
</li>
))}
</ul>
);
}
// Container: chứa logic, không quan tâm UI
function UserListContainer() {
const { data: users, loading } = useFetch('/api/users');
const handleSelect = (user) => console.log('Selected:', user);
return <UserListView users={users || []} loading={loading} onSelect={handleSelect} />;
}📝 Tóm Tắt
- Compound Components: nhóm components chia sẻ state qua Context
- Render Props: component cung cấp logic, consumer quyết định UI
- Container/Presentational: tách logic và UI
- Custom Hooks (2024+): pattern phổ biến nhất hiện nay