Compound Component pattern
Muammodan boshlaylik
Tabs komponenti yozyapsiz. Birinchi fikr — hamma narsani props orqali berish:
<Tabs
tablar={['Profil', 'Sozlamalar', 'Xavfsizlik']}
kontentlar={[<Profil />, <Sozlamalar />, <Xavfsizlik />]}
faolRang="ko'k"
animatsiya={true}
ikonalar={[profilIcon, sozlamalarIcon, xavfsizlikIcon]}
/>
Ishlaydi. Lekin muammo bor.
Bir kuni birinchi tab oldiga badge qo'shish kerak bo'ldi — "3 ta yangi xabar". Props qo'shildi. Keyin bitta tabni disabled qilish kerak bo'ldi. Yana props. Keyin tab o'rtasiga separator qo'yish kerak bo'ldi. Yana props.
Tabs komponenti tobora ko'proq narsani bilishga majbur bo'ldi. Har yangi xususiyat uchun yangi props — komponent murakkablashib ketdi.
Muammoning ildizi: Tabs komponentiga juda ko'p mas'uliyat yuklanmoqda. U tablarning ko'rinishini, kontentni, ikonalarni, badge larni, disabled holatni — hammasini bilishi kerak.
HTML dan o'rganish
Aslida bu muammoning yechimi HTML da allaqachon bor. <select> va <option> ni ko'ring:
<select>
<option value="uz">O'zbek</option>
<option value="ru">Русский</option>
<option value="en" disabled>English (tez kunda)</option>
</select>
<select> tanlash mexanizmini biladi. <option> esa har bir variant nima ekanini biladi. Ikkalasi birgalikda ishlaydi, lekin har biri o'z ishini qiladi.
<option> ga badge qo'shish kerakmi? Shunchaki ichiga yozasiz. <select> ga tegmaysiz. Bu — Compound Component g'oyasi.
React da Compound Component
Tabs ni xuddi <select>/<option> kabi quramiz. Tabs — mexanizmni biladi. Tabs.Tab va Tabs.Panel — har bir qismni biladi.
Ular orasida holat ulashish uchun Context ishlatiladi — tashqaridan ko'rinmaydi, ichki ish.
import { createContext, useContext, useState } from 'react';
const TabsContext = createContext();
function Tabs({ children, boshlangich = 0 }) {
const [faolTab, setFaolTab] = useState(boshlangich);
// faolTab va setFaolTab ni barcha bolalarga yetkazamiz
return (
<TabsContext.Provider value={{ faolTab, setFaolTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabsList({ children }) {
return <div className="tabs-list">{children}</div>;
}
function Tab({ children, index, disabled = false }) {
const { faolTab, setFaolTab } = useContext(TabsContext);
const faol = faolTab === index;
return (
<button
className={`tab ${faol ? 'faol' : ''} ${disabled ? 'disabled' : ''}`}
onClick={() => !disabled && setFaolTab(index)}
aria-selected={faol}
>
{children}
</button>
);
}
function TabPanel({ children, index }) {
const { faolTab } = useContext(TabsContext);
// Faol tab emas — ko'rsatma
if (faolTab !== index) return null;
return <div className="tab-panel">{children}</div>;
}
// Bitta ob'ektga yig'amiz — Tabs.Tab, Tabs.Panel deb ishlatish uchun
Tabs.List = TabsList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
Ishlatish:
<Tabs boshlangich={0}>
<Tabs.List>
<Tabs.Tab index={0}>Profil</Tabs.Tab>
<Tabs.Tab index={1}>
Xabarlar <span className="badge">3</span> {/* badge — Tabs ga tegmay qo'shildi */}
</Tabs.Tab>
<Tabs.Tab index={2} disabled>
Premium (tez kunda)
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel index={0}><Profil /></Tabs.Panel>
<Tabs.Panel index={1}><Xabarlar /></Tabs.Panel>
<Tabs.Panel index={2}><Premium /></Tabs.Panel>
</Tabs>
Badge qo'shish uchun Tabs komponentiga tegmadik — Tabs.Tab ichiga yozdik. Disabled qilish uchun faqat disabled props berdik. Tabs bu haqda bilmaydi ham.
Nima o'zgardi?
Oldin — Tabs hamma narsani bilishi kerak edi:
tablar, kontentlar, ikonalar, badge, disabled, separator...
→ har yangi xususiyat uchun yangi props
Keyin — Tabs faqat mexanizmni biladi:
qaysi tab faol, qaysi tab bosilganda nima bo'ladi
→ qolgan hamma narsa tashqaridan beriladi
Bu farq kichik loyihada sezilmaydi. Lekin komponent o'sganda — birinchi yondashuv bilan komponent boshqarib bo'lmaydigan darajaga yetadi.
Qayerda uchraydi?
Haqiqiy kutubxonalarda bu pattern juda ko'p:
// Radix UI
<Select.Root>
<Select.Trigger />
<Select.Content>
<Select.Item value="uz">O'zbek</Select.Item>
</Select.Content>
</Select.Root>
// Headless UI
<Disclosure>
<Disclosure.Button>Savol</Disclosure.Button>
<Disclosure.Panel>Javob</Disclosure.Panel>
</Disclosure>
Ularni ishlatayotganda bu pattern ni tushunmasangiz — nima uchun bunday yozilganini bilmaysiz.
Xulosa
Compound Component — birgalikda ishlaydigan komponentlar guruhi. Ota komponent mexanizmni (holat) boshqaradi, bola komponentlar esa har bir qismni ko'rsatadi. Ular Context orqali yashirincha gaplashadi.
<Ota> ← holat saqlanadi, Context beradi
<Bola.A /> ← Context dan oladi, o'z ishini qiladi
<Bola.B /> ← Context dan oladi, o'z ishini qiladi
</Ota>
Tabs, Select, Accordion, Dropdown, Menu — bular uchun mos. Foydalanuvchi komponentlarni o'zi yig'adi, har birini mustaqil o'zgartiradi.
Keyingi dars
34-dars: Forward Ref — ref ni child componentga uzatish