JavaRush /Курсы /Модуль 3: React /Когда использовать HOC, а когда — рендер-пропсы — плюсы и...

Когда использовать HOC, а когда — рендер-пропсы — плюсы и минусы подходов

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

Сравнение концепций: HOC vs Рендер-пропсы

Higher-Order Components (HOC)

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

import React from "react";

// HOC для обработки ошибок
function withErrorBoundary<T>(WrappedComponent: React.ComponentType<T>): React.FC<T> {
  return function (props) {
    try {
      return <WrappedComponent {...props} />;
    } catch (error) {
      return <div>Ошибка: {error.message}</div>;
    }
  };
}

// Компонент
function UserProfile({ name }: { name: string }) {
  if (!name) throw new Error("Имя не задано!");
  return <div>Привет, {name}!</div>;
}

// Использование HOC
const SafeUserProfile = withErrorBoundary(UserProfile);

export default function App() {
  return <SafeUserProfile name="Иван" />;
}

Плюсы HOC:

  1. Переиспользуемость логики: логику можно легко применять к разным компонентам. Например, авторизация или обработка ошибок одинаковы для многих частей приложения.
  2. Изоляция логики: логика полностью изолирована от самого компонента. Вы просто добавляете "магическую обертку", а компонент остается чистым.
  3. Совместимость с компонентами любой сложности: HOC работают как с функциональными, так и с классовыми компонентами.

Минусы HOC:

  1. "Ад HOC-ов": когда вы начинаете использовать слишком много HOC в одном и том же компоненте, код становится сложным для понимания. Представьте себе вложенные обертки, как в куске пирога, где вы уже не можете добраться до начинки.
  2. Проблемы с пропсами: иногда сложно отследить, какие пропсы передаются в изначальный компонент, а какие — добавляются HOC.
  3. Проблемы с именами компонентов: при использовании нескольких HOC можно потерять читаемое имя компонента в дебаге. Для борьбы с этим можно использовать displayName.

Рендер-пропсы

Рендер-пропсы используют функцию, переданную в качестве пропса, чтобы контролировать то, что происходит "внутри" компонента. Это дает большую гибкость.

Пример: компонент для получения размера окна.

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

type WindowSizeProps = {
  render: (size: { width: number; height: number }) => React.ReactNode;
};

function WindowSize({ render }: WindowSizeProps) {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

  useEffect(() => {
    const onResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener("resize", onResize);

    return () => {
      window.removeEventListener("resize", onResize);
    };
  }, []);

  return <>{render(size)}</>;
}

// Использование рендер-пропсов
export default function App() {
  return (
    <WindowSize
            render={({ width, height }) => (
        <div>
          Ширина окна: {width}, высота окна: {height}
        </div>
      )}
    />
  );
}

Плюсы рендер-пропсов:

  1. Большая гибкость: передаваемая функция позволяет полностью контролировать, что рендерится внутри компонента. Это делает рендер-пропсы идеальными для сложных UI.
  2. Простота настройки логики: вместо создания нового компонента с HOC, можно просто описать, как компонент должен вести себя.
  3. Отсутствие конфликтов с пропсами: нет необходимости передавать дополнительные пропсы — все делается через переданную функцию.

Минусы рендер-пропсов:

  1. "Ад вложенности": если рендер-пропсы используются неправильно, код может стать сложным из-за вложенности (особенно если передают функции-функциям).
  2. Сложность чтения: когда компоненты с рендер-пропсами начинают вызывать друг друга, читаемость становится сложной.
  3. Снижение читаемости компонента: если передаваемая функция слишком велика или состоит из множества логик, это может засорить основной компонент.

Какой подход использовать? Практическое руководство

Используйте HOC, когда:

  • Вам нужно добавить глобальную логику (например, авторизацию, обработку ошибок).
  • Логика полностью независима от компонента и может быть переиспользована без изменений.
  • Приоритет — изоляция логики: вы хотите хранить основной компонент "чистым" и оборачивать его.
  • Вам нужно применить один и тот же "эффект" сразу ко многим компонентам.

Пример: авторизация.

function withAuthGuard<T>(WrappedComponent: React.ComponentType<T>) {
  return function (props: T) {
    const isAuthenticated = Boolean(localStorage.getItem("token"));
    if (!isAuthenticated) return <div>Пожалуйста, войдите!</div>;
    return <WrappedComponent {...props} />;
  };
}

Используйте рендер-пропсы, когда:

  • Вам нужна гибкость управления рендерингом, где входные данные или состояние зависят от внешних условий.
  • Логика сильно зависима от других частей JSX или должна передавать данные динамически.
  • Вы хотите управлять тем, как именно компонент рендерится, непосредственно в компоненте.

Пример: динамический UI.

export default function App() {
  return (
    <WindowSize
      render={({ width }) => (
        <div>
          {width > 500 ? <h1>Большой экран</h1> : <h2>Маленький экран</h2>}
        </div>
      )}
    />
  );
}

Типичные ошибки

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

Таблица сравнения

Критерий HOC Рендер-пропсы
Гибкость Низкая — логика заранее определена в HOC Высокая — передаваемая функция задает поведение
Переиспользуемость Отличная — подходит для повторяющейся логики Хорошая, но может потребовать больше кода
Чистота компонента Высокая — логика изолирована Низкая — логика часто смешивается с JSX
Сложность внедрения Средняя — требует отдельного написания HOC Высокая — требует написания гибких функций
Конфликты с пропсами Проблемы могут быть из-за дублирующихся пропсов Конфликт исключен, все через render-пропс

Итак, теперь при встрече с задачей абстракции в React у вас есть четкий набор критериев, который поможет вам выбрать между HOC и рендер-пропсами. Надеюсь, вы больше никогда не запутаетесь! 😉

1
Задача
Модуль 3: React, 4 уровень, 8 лекция
Недоступна
Реализация простого HOC для логирования
Реализация простого HOC для логирования
1
Задача
Модуль 3: React, 4 уровень, 8 лекция
Недоступна
Использование рендер-пропсов для изменения состояния
Использование рендер-пропсов для изменения состояния
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ