Render Props паттерн

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

Основы паттерна

В Preact компонент с Render Props принимает функцию в качестве пропса (обычно с именем render или аналогичным) и вызывает её внутри своего рендера, передавая необходимое состояние или методы:

import { h, Component } from 'preact';

class MouseTracker extends Component {
  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>
    );
  }
}

export default function App() {
  return (
    <MouseTracker render={({ x, y }) => (
      <p>Координаты мыши: {x}, {y}</p>
    )} />
  );
}

Ключевой момент: MouseTracker не определяет, как именно будут отображены координаты мыши. Он только предоставляет данные через функцию-проп.

Преимущества Render Props

  1. Повторное использование логики Один и тот же компонент может использоваться с разным визуальным представлением, сохраняя общий функционал.

  2. Изоляция состояния Состояние компонента управляется внутри него, а не в родителе, что упрощает поддержку и тестирование.

  3. Гибкость отображения Можно передавать любые функции для рендера, включая сложные JSX-структуры, условия и анимации.

Использование с хуками

В Preact, начиная с версии X, можно комбинировать Render Props с хуками для управления состоянием без классов:

import { h } from 'preact';
import { useState } from 'preact/hooks';

function Toggle({ render }) {
  const [on, setOn] = useState(false);
  const toggle = () => setOn(prev => !prev);

  return render({ on, toggle });
}

export default function App() {
  return (
    <Toggle render={({ on, toggle }) => (
      <button onCl ick={toggle}>
        {on ? 'Включено' : 'Выключено'}
      </button>
    )} />
  );
}

Паттерн сохраняет преимущества классовых компонентов, но уменьшает количество кода и упрощает синтаксис.

Передача нескольких функций

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

function Form({ render }) {
  const [value, setValue] = useState('');

  const handleChange = (e) => setValue(e.target.value);
  const reset = () => setValue('');

  return render({ value, handleChange, reset });
}

export default function App() {
  return (
    <Form render={({ value, handleChange, reset }) => (
      <div>
        <input value={value} onIn put={handleChange} />
        <button onCl ick={reset}>Сбросить</button>
      </div>
    )} />
  );
}

Такой подход минимизирует дублирование кода и обеспечивает явное управление состоянием.

Интеграция с TypeScript

Render Props хорошо типизируются, что позволяет строго определять передаваемые данные:

interface MouseProps {
  x: number;
  y: number;
}

interface MouseTrackerProps {
  render: (props: MouseProps) => JSX.Element;
}

const MouseTracker: preact.FunctionComponent<MouseTrackerProps> = ({ render }) => {
  const [coords, setCoords] = useState({ x: 0, y: 0 });

  const handleMouseMove = (event: MouseEvent) => {
    setCoords({ x: event.clientX, y: event.clientY });
  };

  return <div onMouseM ove={handleMouseMove}>{render(coords)}</div>;
};

Типизация повышает надёжность и удобство использования компонентов с Render Props в больших приложениях.

Совмещение с контекстом

Render Props отлично сочетается с Preact Context для передачи состояния глубоко вложенным компонентам без проброса через все уровни:

import { createContext } from 'preact';
import { useContext } from 'preact/hooks';

const ThemeContext = createContext('light');

function Themed({ render }) {
  const theme = useContext(ThemeContext);
  return render(theme);
}

export default function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Themed render={(theme) => <p>Текущая тема: {theme}</p>} />
    </ThemeContext.Provider>
  );
}

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

Рекомендации по использованию

  • Использовать Render Props для повторно используемой логики, не зависящей от конкретного UI.
  • Избегать вложенных Render Props (callback hell), если можно использовать хуки или композицию компонентов.
  • Обеспечивать читаемость и предсказуемость: функция-проп должна быть простой и возвращать один JSX-блок.

Render Props остаётся актуальным инструментом проектирования компонентов в Preact, особенно в случаях, когда необходима высокая гибкость и повторное использование логики без жёсткой привязки к визуальной реализации.