Как использовать Render Props в React?react-47

Основная концепция

Render Props (рендер-пропсы) — это продвинутый паттерн в React, при котором компонент получает функцию через пропс (обычно render или children), определяющую что рендерить, и вызывает её, передавая необходимые данные.

Ключевые характеристики:

  • Компонент содержит логику, но не определяет UI
  • UI определяется переданной функцией-рендером
  • Позволяет эффективно повторно использовать логику

Базовый синтаксис

1. Через пропс render:

<DataProvider
  render={(data) => <DisplayComponent data={data} />}
/>

2. Через пропс children :

<DataProvider>
  {(data) => <DisplayComponent data={data} />}
</DataProvider>

Пошаговая реализация

1. Создаём компонент с Render Prop

class MouseTracker extends React.Component {
  state = { x: 0, y: 0 };

  handleMouseMove = (event) => {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  };

  render() {
    return (
      <div onMouseMove={this.handleMouseMove}>
        {/* Вызываем функцию и передаём текущее состояние */}
        {this.props.children(this.state)}
      </div>
    );
  }
}

2. Используем компонент

<MouseTracker>
  {({ x, y }) => (
    <div>
      <h1>Позиция курсора</h1>
      <p>X: {x}, Y: {y}</p>
    </div>
  )}
</MouseTracker>

Практические примеры

1. Компонент для загрузки данных

class DataFetcher extends React.Component {
  state = { data: null, loading: true, error: null };

  async componentDidMount() {
    try {
      const response = await fetch(this.props.url);
      const data = await response.json();
      this.setState({ data, loading: false });
    } catch (error) {
      this.setState({ error, loading: false });
    }
  }

  render() {
    return this.props.children(this.state);
  }
}

// Использование
<DataFetcher url="/api/user">
  {({ data, loading, error }) => {
    if (loading) return <Spinner />;
    if (error) return <Error message={error.message} />;
    return <UserProfile data={data} />;
  }}
</DataFetcher>

2. Компонент-переключатель

class Toggle extends React.Component {
  state = { on: false };

  toggle = () => this.setState(prev => ({ on: !prev.on }));

  render() {
    return this.props.children({
      on: this.state.on,
      toggle: this.toggle
    });
  }
}

// Использование
<Toggle>
  {({ on, toggle }) => (
    <button onClick={toggle}>
      {on ? 'Включено' : 'Выключено'}
    </button>
  )}
</Toggle>

Продвинутые техники

1. Комбинирование нескольких Render Props

<ThemeProvider>
  {theme => (
    <LocaleProvider>
      {locale => (
        <Page theme={theme} locale={locale} />
      )}
    </LocaleProvider>
  )}
</ThemeProvider>

2. Оптимизация производительности

// Мемоизация рендер-функции
class OptimizedComponent extends React.Component {
  renderContent = (data) => {
    return <ExpensiveComponent data={data} />;
  };

  render() {
    return (
      <DataProvider>
        {this.renderContent}
      </DataProvider>
    );
  }
}

3. Деструктуризация пропсов

<Mouse>
  {({ x, y }) => <Cursor x={x} y={y} />}
</Mouse>

Лучшие практики

  1. Именование: Используйте children как функцию для основного случая
  2. Типизация: Добавляйте PropTypes/TypeScript для рендер-функции
  3. Обработка ошибок: Всегда проверяйте, что переданная функция
  4. Документация: Чётко документируйте какие параметры получает рендер-функция

Ограничения

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

Альтернативы

1. Кастомные хуки

function useMousePosition() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  // Логика отслеживания мыши...
  return position;
}

// Использование
const { x, y } = useMousePosition();

2. HOC

const withMousePosition = (Component) => {
  return class extends React.Component {
    // Логика отслеживания мыши...
    render() {
      return <Component {...this.props} mouse={this.state} />;
    }
  };
};

Резюмируем

  1. Render Props — мощный паттерн для разделения логики и представления
  2. Основные способы использования:
    • Через пропс render
    • Через пропс children как функцию
  3. Идеально подходит для:
    • Повторного использования логики
    • Работы с состоянием
    • Загрузки данных
  4. В новых проектах часто заменяется кастомными хуками
  5. Все ещё актуален в:
    • Классовых компонентах
    • Библиотеках (React Router, Formik)
    • Сложных сценариях композиции

Пример полной реализации:

// Компонент с Render Prop
class ScrollPosition extends React.Component {
  state = { scrollY: 0 };

  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll);
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll);
  }

  handleScroll = () => {
    this.setState({ scrollY: window.scrollY });
  };

  render() {
    return this.props.children(this.state);
  }
}

// Использование
<ScrollPosition>
  {({ scrollY }) => (
    <header className={scrollY > 100 ? 'scrolled' : ''}>
      Позиция скролла: {scrollY}px
    </header>
  )}
</ScrollPosition>