← Về danh sách bài họcBài 24/25

🎨 Bài 24: React Patterns - Mẫu Thiết Kế

⏱️ Thời gian đọc: 15 phút | 📚 Độ khó: Nâng cao

🎯 Sau bài học này, bạn sẽ:

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