በጀማሪ ደረጃ hooks ምንድን ናቸው የሚለውን አይታችኋል — በቀጣዩ ደረጃ ግን የትኛው መቼ እና ለምን እንደሚያስፈልግ፣ እንዲሁም የየራሳቸው Trade-offs ምን እንደሆኑ እንማራለን። በእውነተኛ (Real) codebase ላይ ምን አይነት ችግሮች ያጋጥማሉ? እንዴትስ ትፈቷቸዋላችሁ?
Custom Hook ሎጂክን (Logic) ከ component ውስጥ በማውጣት በተለያየ ቦታ በድጋሚ (reuse) እንድንጠቀመው ያደርጋል!
Component የ UI ስራ ብቻ ሲሰራ፣ ሎጂኩን ደግሞ Hook ይይዘዋል።
ጀማሪ ስትሆኑ component ውስጥ ሁሉንም ነገር አንድ ላይ ትፅፉ ነበር — አሁን ግን
Separation of Concerns (የስራ ክፍፍል) ያስፈልጋል። UIን እና Logicን ለያይቷቸው!
// ❌ Logic እና UI አንድ ላይ ተቀላቅሏል! function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { setLoading(true); fetch(`/api/users/${userId}`) .then(r => r.json()) .then(setUser) .catch(setError) .finally(() => setLoading(false)); }, [userId]); // 👆 በሌላ ቦታም ይህንኑ ደጋግማችሁ ትፅፋላችሁ if (loading) return <Spinner />; if (error) return <Error />; return <div>{user.name}</div>; }
// ✅ Custom Hook — logic ተለይቷል function useUser(userId) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { /* fetch logic ሁሉ እዚህ ውስጥ ነው */ }, [userId]); return { user, loading, error }; } // Component — የ UI ስራ ብቻ! function UserProfile({ userId }) { const { user, loading, error } = useUser(userId); // ✅ clean! if (loading) return <Spinner />; return <div>{user.name}</div>; }
// ✅ Generic useFetch — ለማንኛውም endpoint የሚሰራ! function useFetch<T>(url: string) { const [data, setData] = useState<T | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { let cancelled = false; // ← Cleanup! Race conditionን ያስቆማል const fetchData = async () => { try { setLoading(true); setError(null); const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); if (!cancelled) setData(json); // component unmount ሲሆን stateን አትቀይሩ } catch (e) { if (!cancelled) setError(e.message); } finally { if (!cancelled) setLoading(false); } }; fetchData(); return () => { cancelled = true; }; // cleanup! }, [url]); return { data, loading, error }; } // ► Usage — ከማንኛውም endpoint ጋር መጠቀም ይቻላል const { data: users } = useFetch<User[]>('/api/users'); const { data: posts } = useFetch<Post[]>('/api/posts'); const { data: product }= useFetch<Product>(`/api/products/${id}`);
let cancelled = false የሚለው አፃፃፍ (pattern) ይህንን ይከላከላል።
ዋናው ጥያቄ — React መቼ ነው re-render የሚያደርገው? State ወይም Props ሲቀየሩ። ችግሩ ምንድን ነው? Parent ሲቀየር (re-render ሲያደርግ) የውስጡ Children ሁሉም re-render ያደርጋሉ! ይህ ሁልጊዜ አስፈላጊ ስላልሆነ መቆም አለበት።
// ── React.memo — Componentን wrap ያደርጋል። Props ካልተቀየሩ በስተቀር re-render አያደርግም። const ExpensiveList = React.memo(({ items }) => { // ① items prop ካልተቀየረ — re-render አያደርግም return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>; }); // ── useMemo — ከባድ ካልኩሌሽንን cache ለማድረግ function Dashboard({ orders }) { // ❌ Bad: ሁልጊዜ re-render ሲሆን በድጋሚ ይሰላል! const total = orders.reduce((s, o) => s + o.amount, 0); // ✅ Good: orders ሲቀየሩ ብቻ ይሰላል const total = useMemo( () => orders.reduce((s, o) => s + o.amount, 0), [orders] // ← dependency ); // ── useCallback — Function referenceን stable (የማይቀየር) ያደርጋል // ❌ Bad: ሁልጊዜ አዲስ function ይፈጠራል! ይህም memoን እንዳይሰራ ያደርገዋል! const handleClick = (id) => deleteOrder(id); // ✅ Good: stable reference — memo works! const handleClick = useCallback((id) => deleteOrder(id), []); return <ExpensiveList items={orders} onDelete={handleClick} />; }
State ቀይሩ — የትኞቹ components re-render ያደርጋሉ? ብርቱካናማ ብልጭታ (Orange flash) = re-render መሆኑን ያሳያል።
Prop Drilling ማለት Propsን በ component ሰንሰለት ውስጥ ለሁሉም ማስተላለፍ ነው። እስከ 2–3 እርከን (levels) ማስተላለፍ ችግር የለውም፤ 5+ እርከን ሲሆን ግን ችግር ነው! ስለዚህ Context ወይም እንደ Zustand ያሉ External state toolsን ተጠቀሙ።
Prop Drilling ምን እንደሚመስል — Visual:
┌─────────────────────────────────────────────────┐ │ STATE MANAGEMENT DECISION TREE │ └─────────────────────────────────────────────────┘ useState → Local state ነው። ለአንድ component ብቻ ተጠቀሙበት። ex: form input፣ toggle፣ modal open/close Context → Global state ነው። ለ Theme፣ Auth user፣ Language ይጠቅማል። ⚠️ ቶሎ ቶሎ የሚቀያየር (Frequently changing) state አታስቀምጡበት! (ምክንያቱም የተጠቀሙትን ሁሉ re-render ያደርጋል!) Zustand → ለተወሳሰበ global state ያገለግላል። Actions፣ async ነገሮች ሲኖሩ፣ Context አቅም ሲያንሰው እና Performance ወሳኝ ሲሆን ተጠቀሙበት። React Query → ለ Server state፣ API data fetching፣ Caching እና loading states ተጠቀሙበት። ──────────────────────────────────────────────── RULE: ሁልጊዜ ከቀላሉ ጀምሩ። useState → Context → Zustand → Redux ከመነሻው ወደ ከባዱ አትዝለሉ!
Compound Components ማለት የተለዩ ነገር ግን አብረው የሚሰሩ components ማለት ነው። የ HTMLን <select> እና <option> አስቡ — አብረው ነው የሚሰሩት። በእውነተኛ ፕሮጀክቶች ለ Select፣ Tabs፣ Accordion፣ እና Menu እንጠቀምበታለን።
// ── Compound Tabs Component ───────────────────── const TabsContext = createContext({}); function Tabs({ children, defaultTab }) { const [active, setActive] = useState(defaultTab); return ( <TabsContext.Provider value={{ active, setActive }}> <div className="tabs">{children}</div> </TabsContext.Provider> ); } function Tab({ id, children }) { const { active, setActive } = useContext(TabsContext); return ( <button className={active === id ? 'active' : ''} onClick={() => setActive(id)} >{children}</button> ); } function TabPanel({ id, children }) { const { active } = useContext(TabsContext); return active === id ? <div>{children}</div> : null; } // Sub-components ማያያዝ! Tabs.Tab = Tab; Tabs.TabPanel = TabPanel; // ── አጠቃቀም — ንፁህ እና Flexible የሆነ API ─────────────── function Dashboard() { return ( <Tabs defaultTab="overview"> <div className="tab-list"> <Tabs.Tab id="overview">Overview</Tabs.Tab> <Tabs.Tab id="analytics">Analytics</Tabs.Tab> <Tabs.Tab id="settings">Settings</Tabs.Tab> </div> <Tabs.TabPanel id="overview"><Overview /></Tabs.TabPanel> <Tabs.TabPanel id="analytics"><Analytics /></Tabs.TabPanel> </Tabs> ); }
የ Custom hook ሙከራ — useLocalStorage hookን ሞክሩ!
State update ሲሆን የትኞቹ components re-render ያደርጋሉ? Count ሲቀየር ሁሉም re-render ያደርጋሉ ወይስ memo ያስቆማቸዋል?
የ Context Provider ሙከራ! Value ቀይሩ — ሁሉም Consumers ጋር ይቀየራል!