0Pricing
React Academy · Урок

Полиморфные компоненты со свойством 'as'

Типизируйте свойство 'as', чтобы Button отображался как или , сохраняя корректные типы свойств.

«Полиморфные компоненты со свойством 'as'» — бесплатный урок React Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения React Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс React Academy содержит 4 уроков всего.

Что такое полиморфный компонент?

Полиморфный компонент отрисовывается как разные HTML-элементы или компоненты в зависимости от свойства as — например, Button, который отрисовывается как <a> или <button>, сохраняя корректные типы TypeScript.

Простое свойство as (без TypeScript)

Базовая полиморфная реализация без обобщений — она теряет типобезопасность, но демонстрирует концепцию.

function Box({ as: Tag = 'div', children, ...props }) {
  return <Tag {...props}>{children}</Tag>;
}

// Usage:
<Box as="section" className="hero">...</Box>
<Box as="article">...</Box>

Типизированное свойство as с обобщениями

Добавьте параметр обобщённого типа, чтобы вывести правильные свойства для любого элемента, который отрисовывается через as.

type PolymorphicProps<E extends React.ElementType> = {
  as?: E;
} & Omit<React.ComponentPropsWithoutRef<E>, 'as'>;

function Box<E extends React.ElementType = 'div'>({
  as,
  ...props
}: PolymorphicProps<E>) {
  const Tag = as ?? 'div';
  return <Tag {...props} />;
}

Поддержка ref

Добавьте поддержку forwardRef, сохранив полиморфную типизацию.

type PolymorphicRef<E extends React.ElementType> = React.ComponentPropsWithRef<E>['ref'];

type PolymorphicPropsWithRef<E extends React.ElementType, P = {}> = P &
  Omit<React.ComponentPropsWithRef<E>, keyof P> & { as?: E };

const Button = React.forwardRef(
  <E extends React.ElementType = 'button'>(
    { as, ...props }: PolymorphicPropsWithRef<E>,
    ref: PolymorphicRef<E>
  ) => {
    const Tag = as ?? 'button';
    return <Tag ref={ref} {...props} />;
  }
);

Вывод типов TypeScript на практике

Когда вы задаёте as='a', TypeScript знает, что компонент принимает свойства, специфичные для ссылки, например href и target, но не свойства кнопки, например disabled.

// TypeScript allows href because as='a':
<Box as="a" href="https://example.com">Link</Box>

// TypeScript errors if you pass href to a div:
<Box as="div" href="...">Error!</Box> // TS: href is not a valid div prop

Расширение пользовательскими свойствами

Добавьте свойства, специфичные для компонента, к полиморфной основе, включив их в определение типа.

type TextProps<E extends React.ElementType> = PolymorphicProps<E> & {
  size?: 'sm' | 'md' | 'lg';
  weight?: 'normal' | 'bold';
};

function Text<E extends React.ElementType = 'p'>({
  as,
  size = 'md',
  weight = 'normal',
  className,
  ...props
}: TextProps<E>) {
  const Tag = as ?? 'p';
  return (
    <Tag
      className={[`text-${size}`, `font-${weight}`, className].filter(Boolean).join(' ')}
      {...props}
    />
  );
}

Практический пример: ссылка, отображаемая как RouterLink

Компонент Button отображается как ссылка React Router, если передано свойство to, и как обычная кнопка в противном случае.

import { Link } from 'react-router-dom';

type ButtonProps =
  | { to: string; href?: never } & React.ComponentPropsWithoutRef<typeof Link>
  | { href: string; to?: never } & React.ComponentPropsWithoutRef<'a'>
  | { to?: never; href?: never } & React.ComponentPropsWithoutRef<'button'>;

function Button({ to, href, ...props }: ButtonProps) {
  if (to) return <Link to={to} {...props} />;
  if (href) return <a href={href} {...props} />;
  return <button type="button" {...props} />;
}

Компонент Text дизайн-системы

Полиморфный компонент Text, по умолчанию отображаемый как <p>, но способный отображаться как h1–h6, span или label.

<Text as="h1" size="xl">Page Title</Text>
<Text as="span" weight="bold">Inline bold</Text>
<Text as="label" htmlFor="email">Email</Text> // htmlFor is valid because as='label'

Ограничения для свойства as

Используйте extends React.ElementType, чтобы принимать как строки HTML-элементов ('div', 'a'), так и компоненты React.

function Card<E extends React.ElementType = 'div'>({ as, ...props }: PolymorphicProps<E>) {
  const Tag = as ?? 'div';
  return <Tag {...props} />;
}

// Works with HTML elements:
<Card as="section" aria-label="Products" />
// Works with React components:
<Card as={motion.div} animate={{ opacity: 1 }} />

Как избежать распространённых ошибок

Не передавайте свойство as элементу вместе с остальными свойствами (это недопустимый атрибут HTML). Всегда извлекайте его перед передачей остальных свойств.

// Bad:
function Box({ as: Tag = 'div', ...props }) {
  return <Tag as={...} {...props} />; // 'as' attribute on div is invalid HTML
}

// Good:
function Box({ as: Tag = 'div', ...props }) {
  return <Tag {...props} />; // 'as' is used to pick Tag, not passed to element
}

Особенности производительности

Полиморфные компоненты почти не создают накладных расходов: свойство as — всего лишь переменная, содержащая строку типа элемента или ссылку на компонент.

Быстрая проверка

Почему в полиморфном компоненте следует извлечь свойство as перед передачей остальных свойств элементу?

Итоги

Полиморфные компоненты используют обобщённое свойство as?: E extends React.ElementType, чтобы отрисовываться как любой элемент и при этом сохранять корректные свойства TypeScript. Извлекайте as перед передачей остальных свойств. Используйте React.ComponentPropsWithoutRef, чтобы включить в тип все атрибуты, специфичные для элемента.

Часто задаваемые вопросы

Урок «Полиморфные компоненты со свойством 'as'» бесплатный?

Да — полный текст урока «Полиморфные компоненты со свойством 'as'» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс React Academy, подпишись на CoddyKit PRO. Курс React Academy содержит 4 уроков всего.

Чему я научусь в уроке «Полиморфные компоненты со свойством 'as'»?

Типизируйте свойство 'as', чтобы Button отображался как или , сохраняя корректные типы свойств. Ты практикуешь React Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать React Academy?

Предыдущий опыт не требуется. React Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Полиморфные компоненты со свойством 'as'»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке React Academy?

Да. Каждый урок React Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Размеченные объединения для вариантов компонентов
  2. Условные и отображаемые типы в React
  3. Полиморфные компоненты со свойством 'as'
  4. Типобезопасные формы и контракты ответов API
← Назад к React Academy