Files
Hayden Bleasel 55fae375a4 Update docs, internationalization (#35)
* Update Geistdocs

* Update geistdocs.tsx

* Run translation script

* Delete toc.tsx

* Update route.ts

* Update route.ts
2025-12-04 23:57:35 -08:00

578 lines
13 KiB
Plaintext

---
title: Polimorfizem
description: Kako uporabiti prop `as` za spremembo renderanega HTML elementa ob ohranjanju funkcionalnosti komponente.
---
Prop `as` je temeljni vzorec v sodobnih React knjižnicah komponent, ki vam omogoča, da spremenite osnovni HTML element ali komponento, ki se rendera.
Zaslovel je v knjižnicah, kot so [Styled Components](https://styled-components.com/), [Emotion](https://emotion.sh/), in [Chakra UI](https://chakra-ui.com/); ta vzorec nudi fleksibilnost pri izbiri semantičnega HTML-a ob ohranjanju stilov in vedenja komponente.
Prop `as` omogoča polimorfne komponente — komponente, ki se lahko renderajo kot različne vrste elementov ob ohranjanju svoje osnovne funkcionalnosti:
```tsx
<Button as="a" href="/home">
Go Home
</Button>
<Button as="button" type="submit">
Submit Form
</Button>
<Button as="div" role="button" tabIndex={0}>
Custom Element
</Button>
```
## Razumevanje `as`
Prop `as` vam omogoča, da presežete privzeti tip elementa komponente. Namesto da bi bila komponenta vezana na določen HTML element, jo lahko prilagodite tako, da rendera katerikoli veljavni HTML tag ali celo drugo React komponento.
Na primer:
```tsx
// Default renders as a div
<Box>Content</Box>
// Renders as a section
<Box as="section">Content</Box>
// Renders as a nav
<Box as="nav">Content</Box>
```
To rendera različne HTML elemente:
```html
<!-- Default -->
<div>Content</div>
<!-- With as="section" -->
<section>Content</section>
<!-- With as="nav" -->
<nav>Content</nav>
```
## Metode implementacije
Obstajata dva glavna pristopa za implementacijo polimorfnih komponent: ročna implementacija in uporaba Radix UI komponente `Slot`.
### Ročna implementacija
Implementacija `as` prop-a uporablja dinamično renderanje komponent:
```tsx
// Simplified implementation
function Component({
as: Element = 'div',
children,
...props
}) {
return <Element {...props}>{children}</Element>;
}
// More complete implementation with TypeScript
type PolymorphicProps<E extends React.ElementType> = {
as?: E;
children?: React.ReactNode;
} & React.ComponentPropsWithoutRef<E>;
function Component<E extends React.ElementType = 'div'>({
as,
children,
...props
}: PolymorphicProps<E>) {
const Element = as || 'div';
return <Element {...props}>{children}</Element>;
}
```
Komponenta:
1. Sprejema prop `as` s privzetim tipom elementa
2. Uporabi podan element ali pade nazaj na privzetega
3. Razširi vse ostale prop-e na renderani element
4. Ohranja tipno varnost z TypeScript generiki
### Uporaba Radix UI Slot
[Radix UI](https://www.radix-ui.com/) nudi komponento `Slot`, ki predstavlja močnejšo alternativo vzorcu `as`. Namesto samo spreminjanja tipa elementa `Slot` združi prop-e s podkomponento, omogočajoč kompozicijske vzorce.
Najprej namestite paket:
```package-install
npm install @radix-ui/react-slot
```
Vzorec `asChild` uporablja boolean prop namesto specifičnega tipa elementa:
```tsx
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
const itemVariants = cva(
"rounded-lg border p-4",
{
variants: {
variant: {
default: "bg-white",
primary: "bg-blue-500 text-white",
},
size: {
default: "h-10 px-4",
sm: "h-8 px-3",
lg: "h-12 px-6",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Item({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"div"> &
VariantProps<typeof itemVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div"
return (
<Comp
data-slot="item"
data-variant={variant}
data-size={size}
className={cn(itemVariants({ variant, size, className }))}
{...props}
/>
)
}
```
Zdaj ga lahko uporabite na dva načina:
```tsx
// Default: renders as a div
<Item variant="primary">Content</Item>
// With asChild: merges props with child component
<Item variant="primary" asChild>
<a href="/home">Link with Item styles</a>
</Item>
```
Komponenta `Slot`:
1. Klonira otroški element
2. Združi prop-e komponente (className, data atribute itd.) s prop-i otroka
3. Pravilno posreduje ref-e
4. Ravnanje z združevanjem event handlerjev
### Primerjava: `as` vs `asChild`
**`as` prop (ročna implementacija):**
```tsx
// Explicit element type
<Button as="a" href="/home">Link Button</Button>
<Button as="button" type="submit">Submit Button</Button>
// Simple, predictable API
// Limited to element types
```
**`asChild` z Slot:**
```tsx
// Implicit from child
<Button asChild>
<a href="/home">Link Button</a>
</Button>
<Button asChild>
<button type="submit">Submit Button</button>
</Button>
// More flexible composition
// Works with any component
// Better prop merging
```
**Glavne razlike:**
| Značilnost | `as` prop | `asChild` + Slot |
|---------|-----------|------------------|
| **Stil API** | `<Button as="a">` | `<Button asChild><a /></Button>` |
| **Tip elementa** | Določen v propu | Izvlečen iz otroka |
| **Sestava komponent** | Omejena | Popolna podpora |
| **Združevanje propov** | Osnovno razširjanje | Inteligentno združevanje |
| **Posredovanje referenc** | Potrebna ročna nastavitev | Vgrajeno |
| **Obdelovalci dogodkov** | Lahko pride do konfliktov | Pravilno sestavljeno |
| **Velikost knjižnice** | Brez odvisnosti | Zahteva `@radix-ui/react-slot` |
### Kdaj uporabiti kateri pristop
**Uporabite prop `as`, kadar:**
- želite enostavnejši API
- predvsem preklapljate med HTML elementi
- želite se izogniti dodatnim odvisnostim
- je komponenta preprosta in ne potrebuje kompleksnega združevanja prop-ov
**Uporabite `asChild` + Slot, kadar:**
- potrebujete kompozicijo z drugimi komponentami
- želite samodejno združevanje prop-ov
- gradite knjižnico komponent, podobno Radix UI ali shadcn/ui
- potrebujete zanesljivo posredovanje ref-ov med različnimi tipi komponent
## Ključne prednosti
### 1. Fleksibilnost semantičnega HTML-a
Prop `as` zagotavlja, da lahko vedno uporabite najbolj semantično primeren HTML element:
```tsx
// Navigation container
<Container as="nav" className="navigation">
<NavItems />
</Container>
// Main content area
<Container as="main" className="content">
<Article />
</Container>
// Sidebar
<Container as="aside" className="sidebar">
<Widgets />
</Container>
```
### 2. Ponovna uporabnost komponent
Ena komponenta lahko služi več namenom, brez ustvarjanja variacij:
```tsx
// Text component used for different elements
<Text as="h1" size="2xl">Page Title</Text>
<Text as="p" size="md">Body paragraph</Text>
<Text as="span" size="sm">Inline text</Text>
<Text as="label" size="sm">Form label</Text>
```
### 3. Izboljšave dostopnosti
Izberite elemente, ki nudijo najboljšo dostopnost za vsak kontekst:
```tsx
// Link that looks like a button
<Button as="a" href="/signup">
Sign Up Now
</Button>
// Button that submits a form
<Button as="button" type="submit">
Submit
</Button>
// Heading with button styles
<Button as="h2" role="presentation">
Section Title
</Button>
```
### 4. Integracija sistema stilov
Ohranite dosledne stile pri menjavi elementov:
```tsx
const Card = styled.div`
padding: 1rem;
border-radius: 8px;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`;
// Same styles, different elements
<Card as="article">Article content</Card>
<Card as="section">Section content</Card>
<Card as="li">List item content</Card>
```
## Pogosti primeri uporabe
### Tipografske komponente
Ustvarite fleksibilne tekstovne komponente:
```tsx
function Text({
as: Element = 'span',
variant = 'body',
...props
}) {
const className = cn(
'text-base',
variant === 'heading' && 'text-2xl font-bold',
variant === 'body' && 'text-base',
variant === 'caption' && 'text-sm text-gray-600',
props.className
);
return <Element className={className} {...props} />;
}
// Usage
<Text as="h1" variant="heading">Title</Text>
<Text as="p" variant="body">Paragraph</Text>
<Text as="figcaption" variant="caption">Caption</Text>
```
### Postavitvene komponente
Gradite semantične layoute:
```tsx
function Flex({ as: Element = 'div', ...props }) {
return (
<Element
className={cn('flex', props.className)}
{...props}
/>
);
}
// Semantic HTML
<Flex as="header" className="justify-between">
<Logo />
<Navigation />
</Flex>
<Flex as="main" className="flex-col">
<Content />
</Flex>
```
### Interaktivni elementi
Obravnavajte različne vrste interakcij:
```tsx
function Clickable({ as: Element = 'button', ...props }) {
const isButton = Element === 'button';
const isAnchor = Element === 'a';
return (
<Element
role={!isButton && !isAnchor ? 'button' : undefined}
tabIndex={!isButton && !isAnchor ? 0 : undefined}
{...props}
/>
);
}
// Various clickable elements
<Clickable as="button" onClick={handleClick}>Button</Clickable>
<Clickable as="a" href="/link">Link</Clickable>
<Clickable as="div" onClick={handleClick}>Div Button</Clickable>
```
## Najboljše prakse za TypeScript
### Generični tipi komponent
Ustvarite popolnoma tipno varne polimorfne komponente:
```tsx
type PolymorphicRef<E extends React.ElementType> =
React.ComponentPropsWithRef<E>['ref'];
type PolymorphicProps<
E extends React.ElementType,
Props = {}
> = Props &
Omit<React.ComponentPropsWithoutRef<E>, keyof Props> & {
as?: E;
};
// Component with full type safety
function Component<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicProps<E, { customProp?: string }>) {
const Element = as || 'div';
return <Element {...props} />;
}
```
### Samodejno določanje prop-ov
Samodejno sklepajte prop-e na podlagi elementa:
```tsx
// Props are inferred from the element type
<Component as="a" href="/home">Home</Component> // ✅ href is valid
<Component as="div" href="/home">Home</Component> // ❌ TS error: href not valid on div
<Component as="button" type="submit">Submit</Component> // ✅ type is valid
<Component as="span" type="submit">Submit</Component> // ❌ TS error
```
### Diskriminacijske unije
Uporabite diskriminacijske unije za prop-e specifične za element:
```tsx
type ButtonProps =
| { as: 'button'; type?: 'submit' | 'button' | 'reset' }
| { as: 'a'; href: string; target?: string }
| { as: 'div'; role: 'button'; tabIndex: number };
function Button(props: ButtonProps & { children: React.ReactNode }) {
const Element = props.as;
return <Element {...props} />;
}
```
## Najboljše prakse
### 1. Privzeto uporabite semantične elemente
Izberite smiselne privzete vrednosti, ki predstavljajo najpogostejšo rabo:
```tsx
// ✅ Good defaults
function Article({ as: Element = 'article', ...props }) { }
function Navigation({ as: Element = 'nav', ...props }) { }
function Heading({ as: Element = 'h2', ...props }) { }
// ❌ Too generic
function Component({ as: Element = 'div', ...props }) { }
```
### 2. Dokumentirajte podprte elemente
Jasno navedite, kateri elementi so podprti:
```tsx
interface BoxProps {
/**
* The HTML element to render as
* @default 'div'
* @example 'section', 'article', 'aside', 'main'
*/
as?: 'div' | 'section' | 'article' | 'aside' | 'main' | 'header' | 'footer';
}
```
### 3. Preverite primernost elementa
Opozorite, ko se uporabijo neprimerni elementi:
```tsx
function Button({ as: Element = 'button', ...props }) {
if (__DEV__ && Element === 'div' && !props.role) {
console.warn(
'Button: When using as="div", provide role="button" for accessibility'
);
}
return <Element {...props} />;
}
```
### 4. Pravilno ravnajte z event handlerji
Zagotovite, da event handlerji delujejo čez različne elemente:
```tsx
function Interactive({ as: Element = 'button', onClick, ...props }) {
const handleKeyDown = (e: React.KeyboardEvent) => {
if (Element !== 'button' && (e.key === 'Enter' || e.key === ' ')) {
onClick?.(e as any);
}
};
return (
<Element
onClick={onClick}
onKeyDown={Element !== 'button' ? handleKeyDown : undefined}
{...props}
/>
);
}
```
## Pogoste pasti
### Neveljavna gnezditev HTML
Bodite pozorni na pravila gnezdjenja HTML-a:
```tsx
// ❌ Invalid - button inside button
<Button as="button">
<Button as="button">Nested</Button>
</Button>
// ❌ Invalid - div inside p
<Text as="p">
<Box as="div">Invalid nesting</Box>
</Text>
// ✅ Valid nesting
<Text as="div">
<Box as="div">Valid nesting</Box>
</Text>
```
### Manjkajoči atributi dostopnosti
Ne pozabite dodati ustreznih ARIA atributov:
```tsx
// ❌ Missing accessibility
<Box as="nav">
<MenuItems />
</Box>
// ✅ Proper accessibility
<Box as="nav" aria-label="Main navigation">
<MenuItems />
</Box>
```
### Izguba tipne varnosti
Izogibajte se preveč dovoljšnim tipom:
```tsx
// ❌ Too permissive - no type safety
function Component({ as: Element = 'div', ...props }: any) {
return <Element {...props} />;
}
// ✅ Type safe
function Component<E extends React.ElementType = 'div'>({
as,
...props
}: PolymorphicProps<E>) {
const Element = as || 'div';
return <Element {...props} />;
}
```
### Premisleki glede zmogljivosti
Bodite pozorni na učinke rerenderjev:
```tsx
// ❌ Creates new component on every render
function Parent() {
const CustomDiv = (props) => <div {...props} />;
return <Component as={CustomDiv} />;
}
// ✅ Stable component reference
const CustomDiv = (props) => <div {...props} />;
function Parent() {
return <Component as={CustomDiv} />;
}
```