Умные и глупые компоненты

Haunted — это современная библиотека для создания веб-компонентов с использованием функционального подхода, вдохновленного React Hooks, но на чистом Web Components. В основе разработки с Haunted лежит концепция разделения компонентов на умные и глупые, что помогает структурировать код, сделать его поддерживаемым и легко тестируемым.


Глупые компоненты (Presentational / Stateless)

Глупые компоненты сосредоточены на отображении данных. Они не управляют состоянием приложения и не содержат логики взаимодействия с внешними источниками данных. Основные характеристики:

  • Получают все данные через props (параметры).
  • Не используют хуки состояния (useState) для хранения бизнес-логики.
  • Могут быть легко повторно использованы в разных местах приложения.
  • Преимущественно отвечают за разметку и стилизацию.

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

import { html, component } from 'haunted';

function UserCard({ name, age }) {
  return html`
    <div class="user-card">
      <h2>${name}</h2>
      <p>Возраст: ${age}</p>
    </div>
  `;
}

customElements.define('user-card', component(UserCard));

Здесь UserCard не содержит внутреннего состояния и отображает только переданные значения. Такой компонент легко тестировать и использовать в любом контексте.


Умные компоненты (Stateful / Container)

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

  • Используют хуки состояния (useState, useEffect и др.).
  • Отвечают за получение и обработку данных.
  • Могут управлять дочерними компонентами.
  • Служат связующим звеном между пользовательским интерфейсом и бизнес-логикой.

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

import { html, component } from 'haunted';
import { useState, useEffect } from 'haunted';
import './user-card.js';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);

  return html`
    <div class="user-list">
      ${users.map(user => html`<user-card name=${user.name} age=${user.age}></user-card>`)}
    </div>
  `;
}

customElements.define('user-list', component(UserList));

UserList управляет состоянием списка пользователей и передает данные глупым компонентам UserCard. Такой подход обеспечивает чистое разделение обязанностей и упрощает поддержку кода.


Принципы построения умных и глупых компонентов

  1. Минимизация состояния в глупых компонентах Глупые компоненты должны получать все через props и не хранить состояние. Это облегчает их тестирование и повторное использование.

  2. Композиция через глупые компоненты Умные компоненты строят интерфейс, комбинируя несколько глупых компонентов. Такой подход упрощает управление сложными интерфейсами.

  3. Отделение эффектов и побочных действий Умные компоненты используют useEffect для побочных действий: загрузки данных, подписок, таймеров. Глупые компоненты ограничиваются рендером.

  4. Обработка событий Глупые компоненты могут вызывать callback-функции, переданные через props, но не обрабатывать глобальные события самостоятельно. Умные компоненты принимают эти события и реализуют соответствующую логику.


Пример взаимодействия умного и глупого компонентов с событиями

// Глупый компонент
function Button({ label, onClick }) {
  return html`<button @click=${onClick}>${label}</button>`;
}

customElements.define('app-button', component(Button));

// Умный компонент
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);

  return html`
    <div>
      <p>Счетчик: ${count}</p>
      <app-button label="Увеличить" .onCl ick=${increment}></app-button>
    </div>
  `;
}

customElements.define('counter-component', component(Counter));

Глупый компонент Button просто рендерит кнопку и вызывает переданную функцию при клике. Умный компонент Counter управляет состоянием счетчика и передает обработчик событий.


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

  • Четкое разделение обязанностей повышает читаемость и поддержку кода.
  • Повторное использование компонентов без дублирования логики.
  • Упрощение тестирования, так как глупые компоненты можно проверять изолированно.
  • Масштабируемость, позволяющая строить сложные интерфейсы из небольших, понятных частей.

Советы по проектированию

  • Всегда стремиться к тому, чтобы глупый компонент не имел собственного состояния, кроме локального отображения (например, подсветка при наведении).
  • Умные компоненты должны быть единственным источником данных и событий, передаваемых вниз по дереву компонентов.
  • Разделение компонентов помогает внедрять динамическую подгрузку, lazy loading и оптимизацию рендеринга без переписывания логики отображения.

Разделение на умные и глупые компоненты в Haunted позволяет создавать приложения, которые остаются поддерживаемыми и расширяемыми даже при увеличении сложности интерфейса. Такой подход обеспечивает структурированность кода и облегчает командную разработку.