IlmHamroh
JavaScript Full-stack/18-qism. React chuqur29/42-dars17 daqiqa
Mundarija (24)

Polymorphic komponentlar React'da: as prop, asChild va Slot

Qisqacha: Polymorphic komponent — bitta komponent turli teg yoki boshqa komponent sifatida chiziladi: <Button> ham <button>, ham <a>, ham router havolasi bo'la oladi, stili va xulqi esa o'sha. Ikki usul bor. as props'i: <Button as="a" href="/menyu"> — komponent qaysi tegni chizishni props'dan oladi. asChild (Radix uslubi): <Button asChild><a href="/menyu">…</a></Button> — komponent o'z tegini chizmaydi, balki props'larini yagona bolaga birlashtiradi (Slot). Birlashtirishning qoidalari bor: className qo'shiladi, hodisalar ikkalasi ham chaqiriladi, ref'lar ulanadi.

Bu darsda

  • Tugma ko'rinishidagi havola nega <a><button> yoki onClick + location bilan yasalmasligini tushuntira olasiz.
  • as props'li komponent yozasiz va JSX'dagi kichik harf tuzog'ini taniysiz.
  • ElementType va ComponentPropsWithoutRef<T> bilan as ni tiplaysiz.
  • Slot ni o'zingiz yozasiz: props, hodisalar, className, style va ref birlashtirish.
  • as va asChild ni solishtirib, qaysi birini qachon tanlashni bilasiz.

Oldin bilishingiz kerak: Compound komponentlar va slot naqshi, useRef II: DOM elementlari va ref prop, HOC va render props.

1. Nega bu kerak?

Oldingi darsda eski naqshlarni o'qidik. Endi zamonaviy UI kutubxonalarining sevimli naqshiga o'tamiz.

«Bahor» saytida chiroyli Button komponenti bor: yashil fon, yumaloq burchak, 44 px balandlik. Bosh sahifaga "Menyuni ko'rish" tugmasi kerak — lekin u boshqa sahifaga olib boradi. Bu tugma emas, havola. Sardor uch yo'lni sinadi:

Urinish Nima buziladi
<a href="/menyu"><Button>…</Button></a> HTML qoidasi: bosiladigan element ichida bosiladigan element bo'lmaydi; ekran o'quvchi ikki narsani o'qiydi, Tab ikki marta to'xtaydi
<Button onClick={() => (location.href = "/menyu")}> havola emas: o'rta tugma bilan yangi tabda ochilmaydi, "manzilni nusxalash" yo'q, Malikaning ekran o'quvchisi "tugma" deydi
ButtonLink — alohida komponent, stil nusxasi ikki joyda bir xil CSS; birida o'zgarsa, ikkinchisi eskirib qoladi

Kerakli narsa — Button ning ko'rinishi va xulqi, lekin <a> tegi. Ertaga esa router havolasi (<Link> — SPA routing da o'rganamiz) kerak bo'ladi, keyin <label> sifatida tugma… Har biri uchun yangi komponent yasash — chiqish yo'li emas.

Hayotiy o'xshatish: oshxona formasi. Oshpaz ham, ofitsiant ham bitta brenddagi kiyimni kiyadi — rangi, logotipi bir xil. Lekin oshpazniki fartuk, ofitsiantniki kamzul. "Bahor" uslubi — bitta, kiyim turi — har xil. Polymorphic komponent — uslubni turli "kiyim"ga kiygazadi.

2. as props'i

2.1 Birinchi variant

G'oya: qaysi tegni chizishni props'dan olish. JSX'da teg nomi o'zgaruvchi bo'lishi mumkin — faqat u katta harf bilan boshlansa:

jsx
function Button({ as: Component = "button", className, ...rest }) {
  return (
    <Component
      className={className ? `btn ${className}` : "btn"}
      {...rest}
    />
  );
}

function App() {
  return (
    <div>
      <Button type="submit">Buyurtma berish</Button>
      <p>
        <Button as="a" href="#menyu">
          Menyuni ko'rish
        </Button>
      </p>
    </div>
  );
}

Ekranda:

text
Buyurtma berish
Menyuni ko'rish

Ekranda ikkalasi bir xil ko'rinadi (CSS .btn bir xil), lekin DOM'da biri <button type="submit">, ikkinchisi <a href="#menyu">. as: Component = "button" — parametrda destructuring bilan qayta nomlash va standart qiymat. as berilmasa — <button>.

Component ga satr ("a", "button") ham, komponent funksiyasi ham berilishi mumkin. JSX ikkalasini ham biladi: <Component> — "a" bo'lsa HTML teg, funksiya bo'lsa komponent. Shuning uchun router havolasi ham ishlaydi: <Button as={Link} to="/menyu">.

2.2 Komponent sifatida: router havolasi

as ga HTML teg o'rniga komponent berish ham mumkin. SPA'da sahifalar orasida yurish uchun oddiy <a> emas, router'ning havola komponenti kerak: u sahifani qayta yuklamaydi, faqat URL va ekranni almashtiradi. Router'ni keyingi qismda o'rganamiz, hozir uning o'rnida kichik soxta komponent:

jsx
function FakeRouterLink({ to, ...rest }) {
  return (
    <a
      href={to}
      onClick={(e) => {
        e.preventDefault(); // sahifa qayta yuklanmasin
        console.log("router: o'tish", to);
      }}
      {...rest}
    />
  );
}

function Button({ as: Component = "button", className, ...rest }) {
  return (
    <Component
      className={className ? `btn ${className}` : "btn"}
      {...rest}
    />
  );
}

function App() {
  return (
    <Button as={FakeRouterLink} to="/menyu">
      Menyuni ko'rish
    </Button>
  );
}

Konsolda:

text
router: o'tish /menyu

Ekranda:

text
Menyuni ko'rish

Button ikki narsani qo'shdi (className="btn" va bolalar) va qolganini (to) FakeRouterLink ga uzatdi. U esa to ni href ga aylantirdi. Button router haqida hech narsa bilmaydi — u faqat "kimni chizay" degan savolga props'dan javob oldi. Bitta shart bor: FakeRouterLink o'ziga kelgan qolgan props'ni (className, children) <a> ga yoyishi kerak — {...rest}. Yoymasa, tugma stili yo'qoladi.

2.3 Kichik harf tuzog'i

Sardor qayta nomlashni "ortiqcha" deb olib tashladi:

jsx
function Button({ as = "button", children, ...rest }) {
  return (
    <as className="btn" {...rest}>
      {children}
    </as>
  );
}

function App() {
  return <Button as="a" href="#menyu">Menyu</Button>;
}

Konsolda:

text
The tag <as> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

"<as> tegi bu brauzerda tanilmaydi. React komponentini chizmoqchi bo'lsangiz, nomini katta harf bilan boshlang." JSX qoidasi (JSX I): kichik harfli nom — HTML teg nomi, satr sifatida. <as> — as o'zgaruvchisi emas, "as" nomli teg. Brauzer <as>Menyu</as> ni noma'lum element sifatida chizdi — na havola, na tugma. Davosi — katta harfli o'zgaruvchi: { as: Component } yoki const Tag = as;.

2.4 Teg bilan birga o'zgaradigan narsalar

as faqat teg nomini almashtirmaydi. Har tegning o'z qoidalari bor, komponent ularni hisobga olishi kerak:

  • <button> formada standart holatda submit qiladi. Komponent type="button" ni standart qilishi mumkin — lekin faqat <button> bo'lganda. <a type="button"> — ma'nosiz atribut.
  • <a> da disabled atributi yo'q. Havolani "o'chirish" uchun aria-disabled="true" va href ni olib tashlash kerak.
  • <a href> Enter bilan, <button> esa Enter va Space bilan bosiladi. as="div" qilinsa — klaviatura umuman ishlamaydi.
jsx
function Button({ as: Component = "button", type, ...rest }) {
  const isButton = Component === "button";
  return (
    <Component
      type={isButton ? (type ?? "button") : type}
      {...rest}
    />
  );
}

Polymorphic komponent qanchalik ko'p tegni qo'llasa, shuncha ko'p shu kabi istisnolar paydo bo'ladi. Bu — as ning yashirin narxi.

Tekshirib ko'ring: <Button as="div" onClick={order}>Buyurtma</Button> — sichqoncha bilan ishlaydi. Bobur (faqat klaviatura bilan ishlaydi) nima uchun uni bosa olmaydi?

Javob

<div> fokus olmaydi — Tab unga to'xtamaydi, Enter va Space unga "bosish" hodisasini bermaydi. <button> bularning hammasini brauzerdan tekin oladi. as — teg nomini almashtiradi, xulqni emas. Bosiladigan narsa uchun as faqat bosiladigan teglar orasida tanlansin: button, a, router havolasi.

3. as ni TypeScript bilan tiplash

3.1 ElementType va ComponentPropsWithoutRef<T>

JavaScript'da <Button as="a" href> ishlaydi. Lekin <Button href="/menyu"> (as'siz) ham jim o'tadi — <button href> esa ma'nosiz. TypeScript buni ushlashi uchun props turi as ga bog'liq bo'lishi kerak:

  • ElementType — React'da "chizish mumkin bo'lgan narsa" turi: HTML teg nomi ("a", "button") yoki komponent.
  • ComponentPropsWithoutRef<T> — T element yoki komponent qabul qiladigan props turi. ComponentPropsWithoutRef<"a"> — href, target, rel… ComponentPropsWithoutRef<"button"> — type, disabled… (WithoutRef — ref alohida ko'rib chiqiladi).

Ikkalasini generic bilan bog'laymiz:

tsx
import type { ComponentPropsWithoutRef, ElementType } from "react";

type ButtonProps<T extends ElementType> = {
  as?: T;
} & Omit<ComponentPropsWithoutRef<T>, "as">;

function Button<T extends ElementType = "button">({
  as,
  ...rest
}: ButtonProps<T>) {
  const Component: ElementType = as ?? "button";
  return <Component className="btn" {...rest} />;
}

function App() {
  return (
    <div>
      <Button as="a" href="/menyu">Menyu</Button>
      <Button href="/menyu">Menyu</Button>
    </div>
  );
}

Xato:

text
TS2322: Type '{ href: string; children: string; }' is not assignable to type 'IntrinsicAttributes & { as?: "button" | undefined; } & Omit<Omit<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "as">'.

Birinchi Button — xatosiz: T = "a", demak href — ruxsat. Ikkinchisida as yo'q, T standart "button" ga teng va <button> props'larida href yo'q. Xabar uzun, lekin unda ikki joyga qarash yetadi: boshidagi { href: string; … } (biz bergan props) va as?: "button" (TypeScript tanlagan teg). Ma'nosi: "{ href, children } turi { as?: "button" } & <button> props'lari turiga to'g'ri kelmaydi". Uzun qism — React'ning <button> uchun ichki turlari.

3.2 Narxi

Bu turdagi uchta muammo bor. Birinchisi — ref: uni ham T ga bog'lash kerak (ComponentPropsWithRef<T> va qo'shimcha generic). Ikkinchisi — sekinlik: katta polymorphic tur tsc'ni sezilarli sekinlashtiradi, muharrir avtoto'ldirishi kechikadi. Uchinchisi — o'z props'laringiz (variant) bilan teg props'lari to'qnashsa, Omit zanjiri o'sib boradi. Polymorphic turlarning to'liq varianti — React + TS IV darsida. Ko'p jamoalar aynan shu narx sababli as dan voz kechib, keyingi bo'limdagi yo'lga o'tdi.

4. asChild va Slot

4.1 G'oya: o'z tegini chizmaslik

Radix kutubxonasi boshqacha savol berdi: "Komponent qaysi tegni chizishni bilishi shart emas. Foydalanuvchi tegni o'zi yozsin, komponent esa unga o'z props'larini qo'shsin."

jsx
<Button asChild>
  <a href="/menyu">Menyuni ko'rish</a>
</Button>

asChild bo'lsa, Button <button> chizmaydi. Uning o'rniga Slot degan yordamchi komponent ishlaydi: yagona bolani (<a>) oladi va Button ning props'larini (className, onClick, data-*, ref) unga birlashtiradi. DOM'da faqat bitta element — <a href="/menyu" class="btn">. TypeScript ham oddiy: <a> ning turini JSX o'zi tekshiradi, Button hech qanday generic'siz.

flowchart LR
  B["Button props<br/>className: btn<br/>onClick: log"] --> S{{Slot: birlashtir}}
  C["Bola &lt;a&gt; props<br/>href, className: katta<br/>onClick, ref"] --> S
  S --> R["Bitta &lt;a&gt;<br/>class: btn katta<br/>onClick: ikkalasi<br/>ref: ikkalasi"]

Diagrammada asosiy narsa — o'rtadagi "birlashtir". Props'lar ustma-ust yozilmaydi: har tur o'z qoidasi bilan qo'shiladi.

4.2 Slot ni yozamiz

Birlashtirish qoidalari (Radix'nikiga yaqin):

Props turi Qoida
Oddiy (href, id, data-*) bolaniki yutadi
Hodisa (onClick, onKeyDown…) ikkalasi: avval bolaniki, keyin Slot'niki
className ikkalasi qo'shiladi: "btn katta"
style obyektlar birlashadi, bolaniki ustun
ref ikkalasi ham elementni oladi
jsx
import {
  cloneElement,
  isValidElement,
  useEffect,
  useRef,
} from "react";

function composeRefs(...refs) {
  return (node) => {
    for (const ref of refs) {
      if (typeof ref === "function") ref(node);
      else if (ref) ref.current = node;
    }
  };
}

function mergeProps(slotProps, childProps) {
  const merged = { ...slotProps, ...childProps };
  for (const key in slotProps) {
    const s = slotProps[key];
    const c = childProps[key];
    if (!s || !c) continue;
    if (/^on[A-Z]/.test(key)) {
      merged[key] = (...args) => {
        c(...args);
        s(...args);
      };
    } else if (key === "className") {
      merged.className = `${s} ${c}`;
    } else if (key === "style") {
      merged.style = { ...s, ...c };
    } else if (key === "ref") {
      merged.ref = composeRefs(s, c);
    }
  }
  return merged;
}

function Slot({ children, ...slotProps }) {
  if (!isValidElement(children)) return null;
  const merged = mergeProps(slotProps, children.props);
  return cloneElement(children, merged);
}

function Button({ asChild = false, ...props }) {
  const ownRef = useRef(null); // Button o'zi ham elementni biladi
  useEffect(() => {
    console.log("Button ref:", ownRef.current.tagName);
  }, []);
  const Component = asChild ? Slot : "button";
  return <Component className="btn" ref={ownRef} {...props} />;
}

function App() {
  const linkRef = useRef(null);
  useEffect(() => {
    console.log("a ref:", linkRef.current.className);
  }, []);
  return (
    <Button asChild onClick={() => console.log("Button: bosildi")}>
      <a
        href="#menyu"
        className="katta"
        ref={linkRef}
        onClick={(e) => {
          e.preventDefault();
          console.log("a: bosildi");
        }}
      >
        Menyuni ko'rish
      </a>
    </Button>
  );
}

Konsolda:

text
Button ref: A
a ref: btn katta
a: bosildi
Button: bosildi

Ekranda:

text
Menyuni ko'rish

Natijani qatorma-qator o'qing:

  • Button ref: A — Button ning ichki ref'i <a> ni oldi, garchi Button <a> haqida hech narsa bilmasa ham.
  • a ref: btn katta — foydalanuvchi ref'i ham o'sha elementni oldi va unda ikkala className bor.
  • Bosish — ikki ishlovchi ham ishladi, avval bolaniki. Bolaniki birinchi — chunki u aniqroq: masalan e.preventDefault() ni o'zi hal qilsin.

Uch texnik detal. Birinchisi — isValidElement: u bola haqiqatan React elementi ekanini tekshiradi (matn yoki massiv bo'lsa, hech narsa chizilmaydi). Ikkinchisi — children.props.ref: React 19 da ref oddiy props, shuning uchun bolaning ref'i props ichida turadi (useRef II). Uchinchisi — cloneElement: Compound komponentlar darsida "eski API" deyilgan o'sha funksiya. Bu yerda u o'rinli: shartnoma aniq — yagona, to'g'ridan-to'g'ri bola. Radix'ning Slot i ham ichida cloneElement ishlatadi.

Tekshirib ko'ring: mergeProps dagi { ...slotProps, ...childProps } tartibi teskari bo'lsa ({ ...childProps, ...slotProps }), <Button asChild><a id="menyu-havola"> da nima o'zgaradi?

Javob

Oddiy props'larda Slot'niki yutib chiqardi. Button id bermasa, farq yo'q. Lekin Button biror atributni standart qilib qo'ysa (masalan type="button" yoki role), u foydalanuvchining aniq yozganini yopib qo'yardi. Qoida: foydalanuvchi yozgan narsa — eng aniq niyat, u yutsin. Komponentning qiymatlari — faqat standart.

4.3 asChild ning kuchi: xulqlarni ustma-ust qo'yish

as bitta komponent ichida ishlaydi. asChild esa bir nechta komponent xulqini bitta elementga yig'adi. Radix'da tez-tez uchraydigan yozuv:

jsx
<Tooltip.Trigger asChild>
  <Dialog.Trigger asChild>
    <Button>O'chirish</Button>
  </Dialog.Trigger>
</Tooltip.Trigger>

DOM'da — bitta <button>. Unda tooltip'ning hodisalari (ustiga kelish, fokus), dialog'ning hodisalari (bosilsa ochish, aria-haspopup) va Button ning stili. Uchta ref — bitta elementda. as bilan buni yozib bo'lmaydi: Tooltip.Trigger ham, Dialog.Trigger ham o'z tegini chizgan bo'lardi va ichma-ich tugmalar paydo bo'lardi — «Nega bu kerak?» dagi birinchi xato.

4.4 Shart: bola props va ref'ni qabul qilsin

Slot bolaga props uzatadi. Bola HTML teg bo'lsa — muammo yo'q. O'z komponentingiz bo'lsa, u kelgan props'ni o'z elementiga yoyishi shart:

jsx
// ❌ onClick, className, ref yo'qoladi — Slot'ning mehnati behuda
function MyLink({ href, children }) {
  return <a href={href}>{children}</a>;
}

// ✅ hammasi elementga yetadi
function MyLink(props) {
  return <a {...props} />;
}

Bu — HOC darsidagi {...props} qoidasining aynan o'zi. React 19 dan oldin ref uchun forwardRef ham kerak edi. Endi {...props} ref'ni ham olib o'tadi.

5. as yoki asChild?

as asChild
Yozilishi <Button as="a" href> <Button asChild><a href></Button>
TypeScript murakkab generic, sekin oddiy: bolaning turi
Bir nechta xulqni birlashtirish yo'q bor
DOM komponent chizadi bola chizadi
Kim ishlatadi Chakra, MUI (component), styled-components Radix, shadcn/ui

Uchinchi yo'l ham paydo bo'ldi: Base UI va Ark UI render props'ini ishlatadi — <Button render={<a href="/menyu" />}>. G'oya asChild bilan bir xil (props birlashtiriladi), lekin bolaning o'rniga props orqali. Bu — render props ning yangi hayoti.

Kundalik qoida: o'z kichik loyihangizda polymorphic komponent kerak bo'lmasligi ham mumkin. vazifalar kanonida yo'q: Tugma faqat <button>, havolalar esa oddiy <a> — Komponent API'sini loyihalash darsida bu qarorning sababini ko'rasiz. Polymorphic komponent dizayn tizimida va kutubxonada, ya'ni bitta komponent o'nlab joyda turli teg bilan kerak bo'lganda o'zini oqlaydi.

6. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
The tag <as> is unrecognized kichik harfli o'zgaruvchi teg sifatida as: Component — katta harf
<a type="button">, <a disabled> teg o'zgardi, atributlar yo'q teg bo'yicha shartli atribut
Bosiladigan div, klaviatura ishlamaydi as="div" faqat bosiladigan teglar orasida
asChild bilan stil yoki hodisa yo'qoladi bola komponent props'ni yoymaydi {...props}
React.Children.only expected to receive a single React element child. (Radix) asChild ichida ikki bola yoki matn bitta element
Foydalanuvchining onClick i ishlamaydi birlashtirish o'rniga ustidan yozish hodisalarni zanjirlash

7. Mashqlar

Mashqlar kurs/mashqlar/18/29-polymorphic/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): DOM'da qaysi teg?

  • <Button as="a" href="#">Menyu</Button> — DOM'da:
  • <Button asChild><a href="#">Menyu</a></Button> — DOM'da:
  • <Button>Menyu</Button> (standart) — DOM'da:
  • <Button as={Link} to="/menyu"> — Link ichida <a> chizsa, DOM'da:
Yechim

as — komponent o'zi shu tegni chizadi. asChild — bola (<a>) chiziladi, Button faqat props qo'shadi. Standart holatda — <button>. as={Link} — Button Link komponentini chizadi, u esa <a> ni; DOM'da faqat oxirgi HTML teg qoladi, komponentlar ko'rinmaydi.

2-mashq (o'rta): Heading — sarlavha darajasi

Sahifada sarlavha ko'rinishi va darajasi (h1…h6) har xil bo'lishi mumkin: kartada h3 kerak, lekin katta harf bilan. Heading yozing: as (standart "h2") va size ("katta" yoki "kichik", standart "katta"). className — heading heading-katta kabi. App da as="h3" va as="p" bilan ikki sarlavha. Effektda ikkalasining teg nomi va klassini konsolga chiqaring.

Kutilgan natija:

text
H3 heading heading-katta
P heading heading-kichik
Yechim
jsx
import { useEffect } from "react";

function Heading({ as: Tag = "h2", size = "katta", ...rest }) {
  return <Tag className={`heading heading-${size}`} {...rest} />;
}

function App() {
  useEffect(() => {
    for (const el of document.querySelectorAll(".heading")) {
      console.log(el.tagName, el.className);
    }
  }, []);
  return (
    <div>
      <Heading as="h3">Bugungi taom: Osh</Heading>
      <Heading as="p" size="kichik">
        35 000 so'm
      </Heading>
    </div>
  );
}

Konsolda:

text
H3 heading heading-katta
P heading heading-kichik

Ekranda:

text
Bugungi taom: Osh
35 000 so'm

Daraja (h3) — hujjat tuzilmasi uchun: Malikaning ekran o'quvchisi sarlavhalar ro'yxati bo'yicha yuradi va darajalar ketma-ket bo'lishi kerak. Ko'rinish (size) — CSS uchun. Ularni ajratish polymorphic Heading ning asosiy foydasi: "katta ko'rinsin" deb h1 ni noto'g'ri joyga qo'yish shart emas.

3-mashq (qiyin): Slot ni kuchaytiring

«Slot ni yozamiz» bo'limidagi Slot da style va className bor, lekin disabled holati yo'q. Button ga disabled props'ini qo'shing: asChild bilan havola bo'lsa, disabled atributi emas, aria-disabled="true" qo'yilsin, bosish esa bloklansin (e.preventDefault(), ishlovchilar chaqirilmasin). Oddiy <button> da — oddiy disabled.

Ishora: disabled ni Button da qayta ishlang va Slot ga aria-disabled hamda bloklovchi onClick uzating. Bolaning onClick i ham chaqirilmasligi uchun mergeProps ga "bloklash" qoidasini qo'shing yoki onClickCapture dan foydalaning (Hodisalarni boshqarish — capture fazasi).

Kutilgan natija (havola bosilgandan keyin):

text
aria-disabled: true
Yechim
jsx
import { cloneElement, isValidElement, useEffect } from "react";

function Slot({ children, ...slotProps }) {
  if (!isValidElement(children)) return null;
  const c = children.props;
  return cloneElement(children, {
    ...slotProps,
    ...c,
    className: [slotProps.className, c.className]
      .filter(Boolean)
      .join(" "),
  });
}

function Button({ asChild = false, disabled = false, ...props }) {
  if (!asChild) {
    return <button className="btn" disabled={disabled} {...props} />;
  }
  const blocked = disabled
    ? {
        "aria-disabled": true,
        // capture: bolaning onClick idan OLDIN ishlaydi
        onClickCapture: (e) => {
          e.preventDefault();
          e.stopPropagation();
        },
      }
    : {};
  return <Slot className="btn" {...blocked} {...props} />;
}

function App() {
  useEffect(() => {
    const a = document.querySelector("a");
    console.log("aria-disabled:", a.getAttribute("aria-disabled"));
  }, []);
  return (
    <Button asChild disabled>
      <a href="#menyu" onClick={() => console.log("o'tdi!")}>
        Menyu (yopiq)
      </a>
    </Button>
  );
}

Konsolda:

text
aria-disabled: true

Ekranda:

text
Menyu (yopiq)

Havola bosildi, lekin o'tdi! chiqmadi. Sabab — onClickCapture, capture fazasidagi ishlovchi: hodisa elementga yetib bormasdan, yo'lda ushlanadi. Undagi stopPropagation bubble fazasini — bolaning onClick ini — to'xtatdi. preventDefault esa havolaga o'tishni to'xtatdi. <a> da disabled atributi yo'q, shuning uchun holat aria-disabled bilan bildiriladi — ekran o'quvchi "o'chirilgan havola" deydi. Haqiqiy kutubxonalarda havolaning href i ham olib tashlanadi va tabIndex={-1} qo'yiladi, shunda u Tab'da ham to'xtamaydi.

8. Real ishda

  • shadcn/ui va Radix: Button, DropdownMenuTrigger, DialogTrigger — hammasida asChild. shadcn Button aynan @radix-ui/react-slot ni ishlatadi: const Comp = asChild ? Slot : "button" (shadcn/ui da).
  • Router bilan: <Button asChild><Link to="/menyu">…</Link></Button> — tugma ko'rinishidagi SPA havolasi; React Router va Next.js loyihalarida har kuni.
  • Chakra, MUI: as va component props'lari — eski va hozirgi loyihalarda keng tarqalgan.
  • Intervyu savollari: "Polymorphic komponent nima?", "as va asChild farqi?", "Slot props'larni qanday birlashtiradi?", "Nega <a> ichida <button> yomon?".

Xulosa

  • Polymorphic komponent — bitta uslub va xulq, turli teg: tugma, havola, router havolasi.
  • as props'i: { as: Component = "button" } — katta harfli o'zgaruvchi; kichik harf <as> tegini yasaydi.
  • Teg o'zgarsa, qoidalar ham o'zgaradi: type, disabled, klaviatura — faqat mos teglar orasida tanlang.
  • as ning TypeScript turi — ElementType + ComponentPropsWithoutRef<T> generic; kuchli, lekin murakkab va sekin.
  • asChild + Slot — komponent o'z tegini chizmaydi, props'larini yagona bolaga birlashtiradi: hodisalar zanjiri, className qo'shiladi, ref'lar ulanadi.
  • asChild bir nechta xulqni bitta elementga yig'adi; bola props va ref'ni yoyishi shart.

Keyingi dars: Headless komponentlar va prop getters — xulq hookda, ko'rinish to'liq foydalanuvchida: vazifalar dagi useMenyu ning ichi.

Manbalar

  • Radix Primitives: "Composition" (asChild) — radix-ui.com/primitives/docs/guides/composition
  • Radix: Slot utility — radix-ui.com/primitives/docs/utilities/slot
  • React: cloneElement, isValidElement — react.dev/reference/react/cloneElement
  • Base UI: "Composition" (render prop) — base-ui.com/react/handbook/composition
  • HTML Standard: "interactive content" (a ichida button yo'q) — html.spec.whatwg.org/multipage/text-level-semantics.html#the-a-element
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Polymorphic komponentlar React'da: as prop, asChild va Slot — IlmHamroh