Render Props паттерн

Render Props — это паттерн проектирования компонентов, позволяющий делегировать управление тем, как должен рендериться UI, другому компоненту или функции. В контексте Inferno, Render Props применяется для создания компонентов с высокой переиспользуемостью, гибкостью и минимизацией дублирования кода.

Основная идея

Компонент с Render Props не жёстко задаёт разметку, а предоставляет функцию через пропс, которую вызывают внутри render-метода. Эта функция возвращает JSX или виртуальные узлы Inferno, определяя окончательный внешний вид компонента.

Пример базового компонента с Render Props:

import { Component } from 'inferno';

class MouseTracker extends Component {
  constructor(props) {
    super(props);
    this.state = { x: 0, y: 0 };
  }

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

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseM ove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

В этом примере MouseTracker не определяет конкретный визуальный результат. Вместо этого он передаёт текущее состояние мыши через функцию render, переданную через пропсы.

Использование Render Props

Применение компонента:

<MouseTracker render={({ x, y }) => (
  <h1>Положение мыши: ({x}, {y})</h1>
)} />

Преимущества такого подхода:

  • Гибкость: UI полностью определяется вызывающим компонентом.
  • Повторное использование логики: Можно использовать один компонент для отслеживания состояний или событий, создавая разные интерфейсы.
  • Чистота кода: Нет необходимости наследоваться или создавать множество специализированных компонентов.

Render Props для асинхронных данных

Render Props отлично подходят для работы с асинхронными запросами, состоянием загрузки и ошибками.

Пример:

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

  componentDidMount() {
    fetch(this.props.url)
      .then(res => res.json())
      .then(data => this.setState({ data, loading: false }))
      .catch(error => this.setState({ error, loading: false }));
  }

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

Использование:

<DataFetcher url="/api/users" render={({ data, loading, error }) => {
  if (loading) return <p>Загрузка...</p>;
  if (error) return <p>Ошибка загрузки данных</p>;
  return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}} />

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

Комбинация с другими паттернами

Render Props легко сочетается с HOC (Higher-Order Components) или Hooks-подобной логикой в Inferno. Это позволяет создавать мощные абстракции, например:

  • Компоненты для отслеживания состояния элементов (hover, focus, drag-and-drop).
  • Компоненты для управления формами и валидацией.
  • Компоненты для подключения к глобальному состоянию или контексту.

Потенциальные подводные камни

  • Избыточный рендер: Если функция render создаётся как inline-функция при каждом рендере родителя, это может привести к частым обновлениям дочернего компонента. Решение — мемоизация или вынесение функции в отдельный метод.
  • Глубокие вложенности: Множественные Render Props могут приводить к “prop drilling” и сложной иерархии компонентов. Для сложных случаев рекомендуется использовать контекст или объединять логику в более крупные компоненты.

Итоговая структура

Render Props в Inferno представляет собой мощный инструмент для разделения логики и представления, позволяющий:

  • Делегировать визуальное представление вызывающему компоненту.
  • Создавать переиспользуемые и чистые компоненты.
  • Легко интегрировать с асинхронной логикой, состоянием и событиями.

Использование Render Props способствует чистому, модульному и предсказуемому коду, делая компоненты Inferno более гибкими и масштабируемыми.