Birlashgan komponentlar (Compound Components)
Ba’zida bizda shunga o’xshash accordion komponentlari bo’ladi:
<Accordion
items={[
{ label: "Bir", content: "lorem ipsum, batafsil ma’lumot uchun: https://bir.com" },
{ label: "Ikki", content: "lorem ipsum, batafsil ma’lumot uchun: https://ikki.com" },
{ label: "Uch", content: "lorem ipsum, batafsil ma’lumot uchun: https://uch.com" },
]}
/>
Bu komponent quyidagi kabi ro’yxatni ko’rsatishga mo’ljallangan, faqat bir vaqtning o’zida bitta element ochiq bo’la oladi:
Bir
Ikki
Uch
Ushbu komponentning ichki ishlashi taxminan quyidagicha ko’rinadi:
export const Accordion = ({ items }) => {
const [activeItemIndex, setActiveItemIndex] = useState(0);
return (
<ul>
{items.map((item, index) => (
<li onClick={() => setActiveItemIndex(index)} key={item.id}>
<strong>{item.label}</strong>
{index === activeItemIndex && item.content}
</li>
))}
</ul>
);
};
Ammo, agar biz Ikki
va Uch
elementlari orasida maxsus ajratgich bo’lishini xohlasakchi? Yoki uchinchi havolani qizil rangda bo’lishini istasak-chi? Biz, ehtimol, quyidagi kabi qandaydir hiylaga murojaat qilishimiz mumkin:
<Accordion
items={[
{ label: "Bir", content: "lorem ipsum, batafsil ma’lumot uchun: https://bir.com" },
{ label: "Ikki", content: "lorem ipsum, batafsil ma’lumot uchun: https://ikki.com" },
{ label: "---" },
{ label: "Uch", content: "lorem ipsum, batafsil ma’lumot uchun: https://uch.com" },
]}
/>
Ammo bu ko’rinish biz xohlagandek bo’lmasligi mumkin. Shuning uchun, biz, ehtimol, o’zimiz yaratgan hiylani yanada o’zgartirishimiz kerak bo’ladi:
export const Accordion = ({ items }) => {
const [activeItemIndex, setActiveItemIndex] = useState(0);
return (
<ul>
{items.map((item, index) =>
item === "---" ? (
<hr />
) : (
<li onClick={() => setActiveItemIndex(index)} key={item.id}>
<strong>{item.label}</strong>
{index === activeItemIndex && item.content}
</li>
)
)}
</ul>
);
};
Endi, bu kod bilan faxrlansak bo’ladimi? Ishonchimiz komil emas. Shuning uchun bizga birlashgan komponentlar kerak bo’ladi: ular o’zaro bog’langan, alohida komponentlarni guruhlash imkonini beradi, ular birgalikda state’ni ulashadi, ammo ular avtomatik ravishda render qilinishi mumkin, bu esa bizga elementlar daraxtini boshqarish uchun ko’proq nazoratni taqdim etadi.
Birlashgan komponentlar yordamida Accordion
Birlashgan komponentlar (compound components) pattern’ini ishlatsak, ushbu accordion quyidagicha ko’rinadi:
<Accordion>
<AccordionItem item={{ label: "Bir" }} />
<AccordionItem item={{ label: "Ikki" }} />
<AccordionItem item={{ label: "Uch" }} />
</Accordion>
Agar bu pattern’ni React’da qanday amalga oshirish mumkinligini o’rgansak, ikkita usulni ko’rib chiqishimiz mumkin:
React.cloneElement
yordamida bolalarni boshqarishReact Context
yordamida
React.context qo’shish
React.cloneElement
eski API hisoblanadi, shuning uchun bu masalani React Context yordamida hal qilishni o’rganamiz. Avvalo, accordion’ning har bir qismi o’qiy oladigan context’ni yaratishdan boshlaymiz:
const AccordionContext = createContext({
activeItemIndex: 0,
setActiveItemIndex: () => 0,
});
Keyin, Accordion
komponentimiz context’ni farzandlariga taqdim qiladi:
export const Accordion = ({ children }) => {
const [activeItemIndex, setActiveItemIndex] = useState(0);
return (
<AccordionContext.Provider value={{ activeItemIndex, setActiveItemIndex }}>
<ul>{children}</ul>
</AccordionContext.Provider>
);
};
Endi, alohida AccordionItem
komponentlarini yaratamiz, ular ham ushbu context’ni iste’mol qilib, unga javob beradi:
export const AccordionItem = ({ item, index }) => {
// E’tibor bering, bu yerda state emas, context ishlatilmoqda!
const { activeItemIndex, setActiveItemIndex } = useContext(AccordionContext);
return (
<li onClick={() => setActiveItemIndex(index)} key={item.id}>
<strong>{item.label}</strong>
{index === activeItemIndex && item.content}
</li>
);
};
Endi, Accordion
komponentining bir nechta qismi mavjud bo’lib, uni birlashgan komponentga aylantirdik.
Foydalanishimiz quyidagicha o’zgaradi, avval bunday bo’lgan bo’lsa:
<Accordion
items={[
{ label: "Bir", content: "lorem ipsum, batafsil ma’lumot uchun: https://bir.com" },
{ label: "Ikki", content: "lorem ipsum, batafsil ma’lumot uchun: https://ikki.com" },
{ label: "Uch", content: "lorem ipsum, batafsil ma’lumot uchun: https://uch.com" },
]}
/>
Endi esa bunday:
<Accordion>
{items.map((item, index) => (
<AccordionItem key={item.id} item={item} index={index} />
))}
</Accordion>
Buning afzalligi shundaki, biz ko’proq nazoratga ega bo’lamiz, shu bilan birga har bir AccordionItem
Accordion
ning kattaroq state’idan xabardor bo’ladi. Endi, agar biz Ikki
va Uch
elementlari orasiga gorizontal chiziq qo’shmoqchi bo’lsak, biz map
metodini o’rniga, qo’lda boshqarish yo’lini tanlashimiz mumkin:
<Accordion>
<AccordionItem key={items[0].id} item={items[0]} index={0} />
<AccordionItem key={items[1].id} item={items[1]} index={1} />
<hr />
<AccordionItem key={items[2].id} item={items[2]} index={2} />
</Accordion>
Yoki aralash usulni qo’llashimiz mumkin:
<Accordion>
{items.slice(0, 2).map((item, index) => (
<AccordionItem key={item.id} item={item} index={index} />
))}
<hr />
{items.slice(2).map((item, index) => (
<AccordionItem key={item.id} item={item} index={index} />
))}
</Accordion>
Bu birlashgan komponentlarning afzalligi shundaki, ular boshqaruvni render qilish uchun ota komponentga topshiradi, shu bilan birga farzandlar o’rtasidagi kontekstual holatni saqlab qoladi. Xuddi shu yondashuvni “tab” interfeysida ham qo’llash mumkin, bunda tab’lar joriy tab holatidan xabardor bo’ladi, lekin har xil darajadagi elementlar joylashishi mumkin.
Boshqa bir afzallik shundaki, bu pattern ma’suliyatlarni ajratish(separation of concerns)ni qo’llab-quvvatlaydi, bu esa dasturlarni vaqt o’tishi bilan ancha yaxshi kengaytirishga yordam beradi.