Render Props

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


Основы Render Props

В Atomico компонент — это функция, которая возвращает JSX через html. Для реализации render props создаётся компонент, принимающий свойство-функцию (обычно называют children или render) и вызывающий её внутри своей разметки.

import { c, html, useState } from "atomico";

const Counter = c(
  ({ render }) => {
    const [count, setCount] = useState(0);

    return html`
      <div>
        ${render({ count, increment: () => setCount(count + 1) })}
      </div>
    `;
  }
);

export default Counter;

В этом примере компонент Counter хранит состояние счётчика, а вывод реализуется внешней функцией render. Это позволяет полностью контролировать отображение компонента извне.


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

Вызов компонента с render props выглядит следующим образом:

import Counter from "./Counter";

const App = () => html`
  <${Counter}
    .render=${({ count, increment }) => html`
      <p>Счётчик: ${count}</p>
      <button @click=${increment}>Увеличить</button>
    `}
  />
`;

Здесь логика обновления состояния отделена от визуального представления. Можно создавать несколько разных интерфейсов для одного и того же состояния:

<Counter
  .render=${({ count, increment }) => html`
    <div>
      <strong>${count}</strong>
      <button @click=${increment}>+1</button>
    </div>
  `}
/>

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

  1. Гибкость интерфейса Render props позволяют менять разметку без модификации логики компонента. Один и тот же компонент может отображаться по-разному в разных частях приложения.

  2. Изоляция состояния Компонент контролирует внутреннее состояние и предоставляет его через функцию, снижая риск прямого вмешательства извне.

  3. Переиспользуемость Логику можно использовать с любым шаблоном разметки, что особенно удобно для библиотек UI-компонентов.


Комбинирование с хук-логикой

Atomico активно использует хуки (useState, useEffect, useRef) для управления состоянием. Render props отлично интегрируются с этой моделью:

const Toggle = c(
  ({ render }) => {
    const [on, setOn] = useState(false);

    const toggle = () => setOn(!on);

    return html`${render({ on, toggle })}`;
  }
);

<Toggle
  .render=${({ on, toggle }) => html`
    <button @click=${toggle}>
      ${on ? "Включено" : "Выключено"}
    </button>
  `}
/>

Здесь Toggle предоставляет логику переключателя, а отображение кнопки полностью настраивается снаружи.


Совместимость с JSX и html-тегами

Render props в Atomico легко комбинируются с HTML-разметкой и компонентами:

<Counter
  .render=${({ count, increment }) => html`
    <div class="counter-container">
      <span>Значение: ${count}</span>
      <my-button @click=${increment}>Добавить</my-button>
    </div>
  `}
/>

Можно использовать как встроенные элементы, так и кастомные веб-компоненты. Важно передавать функцию через свойство, а не как обычный дочерний элемент, чтобы Atomico корректно распознал её как render prop.


Обработка нескольких Render Props

Компонент может предоставлять несколько точек для кастомизации через несколько функций:

const List = c(
  ({ items, renderItem, renderEmpty }) => html`
    <ul>
      ${items.length
        ? items.map(item => html`<li>${renderItem(item)}</li>`)
        : renderEmpty()}
    </ul>
  `
);

<List
  .items=${[]}
  .renderItem=${item => html`<strong>${item}</strong>`}
  .renderEmpty=${() => html`<em>Список пуст</em>`}
/>

Такой подход повышает универсальность компонента, позволяя задавать поведение для разных ситуаций.


Ограничения и рекомендации

  • Избыточные рендеры: передача функции через render prop создаёт новую ссылку на каждый рендер. Для оптимизации можно использовать useCallback.
  • Сложность вложенности: при использовании нескольких render props структура может стать громоздкой. Часто используют комбинацию render props и обычных компонентов для упрощения.
  • Передача состояния: важно ограничивать область передаваемого состояния только необходимыми данными, чтобы избежать ненужной зависимости визуальных компонентов от лишней логики.

Вывод

Render props в Atomico — мощный инструмент для разделения логики и представления. Они обеспечивают гибкость, переиспользуемость и контроль состояния компонентов, оставаясь совместимыми с хуками и JSX-подобным синтаксисом Atomico. Правильное применение render props помогает строить чистую архитектуру компонентов и упрощает поддержку больших приложений.