Урок 11

Композиция и переиспользование

React построен на композиции: мелкие компоненты собираются в крупные. Это делает код переиспользуемым и поддерживаемым.

Композиция через children

function Card({ children }: { children: React.ReactNode }) {
    return <div className="card">{children}</div>;
}

<Card>
    <h2>Заголовок</h2>
    <p>Текст</p>
</Card>

Именованные слоты

Если нужно несколько областей — передавай пропсы с JSX:

type LayoutProps = {
    header: React.ReactNode;
    sidebar: React.ReactNode;
    children: React.ReactNode;
};

function Layout({ header, sidebar, children }: LayoutProps) {
    return (
        <div className="layout">
            <header>{header}</header>
            <aside>{sidebar}</aside>
            <main>{children}</main>
        </div>
    );
}

<Layout
    header={<h1>Мой сайт</h1>}
    sidebar={<nav>...</nav>}
>
    <p>Основной контент</p>
</Layout>

Специализация

Один базовый компонент и несколько специализированных:

function Button({ variant, children, ...props }: ButtonProps) {
    return (
        <button className={`btn btn-${variant}`} {...props}>
            {children}
        </button>
    );
}

function PrimaryButton(props: Omit<ButtonProps, 'variant'>) {
    return <Button variant="primary" {...props} />;
}

function DangerButton(props: Omit<ButtonProps, 'variant'>) {
    return <Button variant="danger" {...props} />;
}

Omit<ButtonProps, 'variant'> исключает пропс variant из типа.

Spread пропсов

{...props} передаёт все пропсы дальше. Полезно для обёрток:

function Input(props: React.InputHTMLAttributes<HTMLInputElement>) {
    return <input className="input" {...props} />;
}

<Input type="email" placeholder="Email" />

Композиция функций

Логику выносят в кастомные хуки:

function useToggle(initial = false) {
    const [value, setValue] = useState(initial);

    function toggle() {
        setValue((v) => !v);
    }

    return { value, toggle };
}

function Modal() {
    const { value: isOpen, toggle } = useToggle();

    return (
        <>
            <button onClick={toggle}>Открыть</button>
            {isOpen && <div className="modal">...</div>}
        </>
    );
}

Render props

Компонент принимает функцию, которая возвращает разметку:

type ListProps<T> = {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
};

function List<T>({ items, renderItem }: ListProps<T>) {
    return <ul>{items.map(renderItem)}</ul>;
}

<List
    items={users}
    renderItem={(user) => <li key={user.id}>{user.name}</li>}
/>

Используется реже, чем хуки, но иногда удобен.

Композиция вместо наследования

В React нет наследования компонентов. Вся переиспользуемость — через композицию, пропсы и хуки.

// Плохо: попытка наследования
class AdminButton extends Button {}

// Хорошо: специализация через пропсы
function AdminButton(props) {
    return <Button variant="admin" {...props} />;
}

Итоги

  • Композиция — основной способ переиспользования
  • children — для вложенного контента
  • Именованные слоты — через пропсы с JSX
  • Spread пропсов — для обёрток
  • Логика — в кастомных хуках