JavaRush /Курсы /Модуль 3: React /Разбор кейсов применения HOC и рендер-пропсов

Разбор кейсов применения HOC и рендер-пропсов

Модуль 3: React
4 уровень , 9 лекция
Открыта

Применение HOC и рендер-пропсов в реальных проектах

Когда работаете над реальным проектом, встает вопрос: какой из этих подходов использовать? В теории всё может казаться очевидным, но на практике часто ждут нюансы, которые не сразу бросаются в глаза. Давайте разберём несколько кейсов, чтобы понять, в каких ситуациях лучше подходят HOC, а в каких — рендер-пропсы.

Кейсы использования HOC

Пример 1: Авторизация пользователей

Представьте, что вам нужно защитить некоторые маршруты приложения, чтобы только авторизованные пользователи могли их видеть. HOC прекрасно подходит для этого. Мы создаём компонент withAuth, который проверяет, авторизован ли пользователь, и либо отображает защищённый компонент, либо перенаправляет на страницу логина.

import React from 'react';
import { Navigate } from 'react-router-dom';

interface WithAuthProps {
  isAuthenticated: boolean;
}

const withAuth = <P extends object>(
  WrappedComponent: React.ComponentType>P>
) => {
  return (props: P & WithAuthProps) => {
    const { isAuthenticated, ...restProps } = props;

    if (!isAuthenticated) {
      return <Navigate to="/login" replace />;
    }

    return <WrappedComponent {...(restProps as P)} />;
  };
};

// Пример использования
const ProtectedPage: React.FC = () => <h1>Секретная страница</h1>;

export default withAuth(ProtectedPage);

Здесь HOC позволяет добавить логику проверки авторизации однократно и переиспользовать её во всём приложении. Это особенно полезно, если защищённых компонентов много.

Пример 2: Кэширование данных

HOC также хорош для добавления логики кэширования, чтобы избежать лишних API-запросов. Представьте компонент, отображающий список товаров. Мы можем создать HOC withCache, который сохранит данные в локальном хранилище и повторно использует их при следующем рендере.

import React, { useEffect, useState } from 'react';

const withCache = <P extends object>(
  WrappedComponent: React.ComponentType<P>,
  cacheKey: string
) => {
  return (props: P) => {
    const [data, setData] = useState<any | null>(null);

    useEffect(() => {
      const cachedData = localStorage.getItem(cacheKey);
      if (cachedData) {
        setData(JSON.parse(cachedData));
      } else {
        // Симулируем API-запрос
        const fetchData = async () => {
          const response = await fetch('/api/data');
          const result = await response.json();
          localStorage.setItem(cacheKey, JSON.stringify(result));
          setData(result);
        };
        fetchData();
      }
    }, [cacheKey]);

    if (!data) {
      return <p>Загрузка...</p>;
    }

    return <WrappedComponent {...props} />;
  };
};

// Пример использования HOC
const ProductList: React.FC = () => <div>Список товаров</div>;

export default withCache(ProductList, 'productListCache');

Кейсы использования рендер-пропсов

Пример 1: Обработка формы с динамическими состояниями

Рендер-пропсы отлично подходят, когда компоненту нужно предоставить динамически изменяющиеся состояния. Например, создадим компонент FormProvider, который будет управлять состоянием формы и предоставлять его с помощью рендер-пропсов.

import React, { useState } from 'react';

interface FormProviderProps {
  children: (formState: { values: Record<string, string>; handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void }) => React.ReactNode;
}

const FormProvider: React.FC<FormProviderProps> = ({ children }) => {
  const [formValues, setFormValues] = useState<Record<string, string>>({});

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormValues((prevValues) => ({
      ...prevValues,
      [e.target.name]: e.target.value,
    }));
  };

  return <>{children({ values: formValues, handleChange })}</>;
};

// Используем рендер-пропсы
const LoginForm: React.FC = () => (
  <FormProvider>
    {({ values, handleChange }) => (
      <form>
        <input name="username" value={values.username || ''} onChange={handleChange} placeholder="Логин" />
        <input name="password" type="password" value={values.password || ''} onChange={handleChange} placeholder="Пароль" />
        <button type="submit">Войти</button>
      </form>
    )}
  </FormProvider>
);

Это подход позволяет компоненту-форме полностью контролировать своё отображение, в то время как управление состоянием остаётся в FormProvider.

Пример 2: Анимации

Допустим, мы хотим анимировать компонент при его появлении. Рендер-пропсы можно использовать для предоставления логики анимации.

import React from 'react';

interface AnimationProviderProps {
  children: (isVisible: boolean) => React.ReactNode;
}

const AnimationProvider: React.FC<AnimationProviderProps> = ({ children }) => {
  const [isVisible, setIsVisible] = React.useState(false);

  React.useEffect(() => {
    const timeout = setTimeout(() => setIsVisible(true), 300);
    return () => clearTimeout(timeout);
  }, []);

  return <>{children(isVisible)}</>;
};

// Используем анимацию
const AnimatedComponent: React.FC = () => (
  <AnimationProvider>
    {(isVisible) => (
      <div style={{ opacity: isVisible ? 1 : 0, transition: 'opacity 0.3s' }}>
        Плавное появление!
</div>
)}
</AnimationProvider>
);

Типичные ошибки и как их избежать

  1. Избыточная вложенность компонентов. При использовании HOC или рендер-пропсов легко запутаться в слишком глубокой вложенности. Например, несколько HOC могут сильно усложнить чтение кода. Порядок HOC следует продумывать заранее, а рендер-пропсы лучше использовать в ограниченных случаях.

  2. Неверная типизация. Не забывайте тщательно типизировать пропсы HOC и функции рендер-пропсов. TypeScript тут не просто помощник, а настоящий спасатель от потенциальных багов.

  3. Неправильный выбор подхода. Если вам нужно добавить только одну функцию (например, логгирование), не используйте для этого рендер-пропсы. Для таких задач HOC будет гораздо проще.

  4. Проблемы с производительностью. HOC могут вызывать повторное создание компонентов. Используйте React.memo и следите за оптимизацией лишних рендеров.

HOC vs Рендер-пропсы: что выбрать?

Выбор всегда зависит от задачи. HOC хороши для строгой логики и повторного использования кода, а рендер-пропсы гибки и позволяют дочерним компонентам управлять своим отображением. Например:

  • Нужна авторизация? Используйте HOC.
  • Требуется предоставить динамическую функциональность? Рендер-пропсы.
  • Хочешь сделать код чище при добавлении обёрток? HOC.
  • Нужно предоставить управление состоянием? Рендер-пропсы.

Помните, что оба подхода — инструменты. Выбирайте тот, что лучше решает вашу задачу.

1
Задача
Модуль 3: React, 4 уровень, 9 лекция
Недоступна
Реализация рендер-пропсов для таймера
Реализация рендер-пропсов для таймера
1
Задача
Модуль 3: React, 4 уровень, 9 лекция
Недоступна
HOC для обработки ошибок
HOC для обработки ошибок
3
Опрос
Создание компонента с рендер-пропсами, 4 уровень, 9 лекция
Недоступен
Создание компонента с рендер-пропсами
Создание компонента с рендер-пропсами
Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Антон Уровень 90
10 июля 2025
использование <WrappedComponent {...props} /> в export function withErrorBoundary<P> выдает постоянно ошибку - <html>TS2769: No overload matches this call.<br/>Overload 1 of 2, '(props: P, context?: any): ReactElement<any, any> | Component<P, any, any> | null', gave the following error.<br/>Type 'P' is not assignable to type 'IntrinsicAttributes & P'.<br/>Type 'P' is not assignable to type 'IntrinsicAttributes'.<br/>Overload 2 of 2, '(props: P): ReactElement<any, any> | Component<P, any, any> | null', gave the following error.<br/>Type 'P' is not assignable to type 'IntrinsicAttributes & P'.