Повторное использование логики

Haunted — это легковесная библиотека для создания веб-компонентов с использованием реактивного подхода, вдохновлённого React Hooks. Одним из ключевых аспектов эффективной разработки в Haunted является повторное использование логики, что позволяет строить масштабируемые и поддерживаемые приложения без дублирования кода.


Функции-компоненты и хуки

В Haunted компоненты создаются как обычные функции JavaScript, возвращающие шаблон html из библиотеки lit-html:

import { html, render } from 'lit-html';
import { component, useState, useEffect } from 'haunted';

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setCount(c => c + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + 1)}>+</button>
      <button @click=${() => setCount(count - 1)}>-</button>
    </div>
  `;
}

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

Ключевые моменты:

  • useState создаёт реактивное состояние, обновляющее DOM при изменении значения.
  • useEffect управляет побочными эффектами, аналогично React.
  • Функции-компоненты можно комбинировать и повторно использовать, создавая сложные интерфейсы из маленьких блоков логики.

Создание повторно используемых хуков

Haunted позволяет создавать собственные хуки, которые инкапсулируют повторяемую логику:

import { useState, useEffect } from 'haunted';

function useTimer(initial = 0) {
  const [time, setTime] = useState(initial);

  useEffect(() => {
    const id = setInterval(() => setTime(t => t + 1), 1000);
    return () => clearInterval(id);
  }, []);

  return time;
}

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

function TimerDisplay() {
  const time = useTimer(10);

  return html`<p>Время: ${time} сек.</p>`;
}

customElements.define('timer-display', component(TimerDisplay));

Преимущества:

  • Вынос логики в отдельную функцию упрощает тестирование.
  • Компоненты становятся более читаемыми, сосредоточенными на отображении.
  • Можно использовать один и тот же хук в нескольких компонентах без дублирования кода.

Параметры и настраиваемость хуков

Хуки в Haunted могут принимать аргументы, что делает их гибкими и адаптируемыми:

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(json => setData(json))
      .finally(() => setLoading(false));
  }, [url]);

  return { data, loading };
}

function UserList() {
  const { data, loading } = useFetch('https://jsonplaceholder.typicode.com/users');

  if (loading) return html`<p>Загрузка...</p>`;

  return html`
    <ul>
      ${data.map(user => html`<li>${user.name}</li>`)}
    </ul>
  `;
}

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

Ключевые особенности:

  • Передача параметров позволяет создавать универсальные хуки, работающие с разными источниками данных.
  • Смена входных значений (url в примере) автоматически инициирует новый эффект.
  • Можно возвращать несколько состояний, объединяя логику в едином хуке.

Комбинирование хуков

Haunted поддерживает составные хуки, что упрощает повторное использование сложной логики:

function useCounterWithTimer(initialCount = 0) {
  const [count, setCount] = useState(initialCount);
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);

  return { count, setCount, seconds };
}

function CounterWithTimer() {
  const { count, setCount, seconds } = useCounterWithTimer();

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <p>Время: ${seconds}</p>
      <button @click=${() => setCount(count + 1)}>+</button>
      <button @click=${() => setCount(count - 1)}>-</button>
    </div>
  `;
}

customElements.define('counter-with-timer', component(CounterWithTimer));

Выводы:

  • Логика состояния и таймера полностью инкапсулирована в хуке.
  • Компонент сосредоточен исключительно на представлении.
  • При необходимости этот же хук можно использовать в других компонентах без изменений.

Использование контекста для совместного состояния

Haunted поддерживает контекст через хуки createContext и useContext, что позволяет поделиться логикой между компонентами, избегая проп-дриллинга:

import { createContext, useContext } from 'haunted';

const ThemeContext = createContext('light');

function ThemeProvider({ theme, children }) {
  return html`<${ThemeContext.Provider} value=${theme}>${children}</${ThemeContext.Provider}>`;
}

function ThemedText() {
  const theme = useContext(ThemeContext);
  return html`<p>Текущая тема: ${theme}</p>`;
}

Преимущества контекста:

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

Рекомендации по архитектуре

  • Разделять логику состояния и логіку представления.
  • Использовать хуки для любой повторяемой логики: таймеры, запросы к API, обработка форм.
  • Создавать универсальные хуки с параметрами, чтобы их можно было применять в разных сценариях.
  • Для глобального состояния применять контекст вместо проп-дриллинга.

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