Производительность и контекст

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


Реактивность и ререндеринг

Haunted использует систему реактивных хуков, главным образом useState и useEffect, чтобы управлять состоянием компонентов. Важной особенностью является локальный ререндеринг, когда изменяется состояние: Haunted обновляет только ту часть DOM, которая зависит от изменённого состояния, что минимизирует накладные расходы.

Пример использования useState:

import { html, component, useState } from 'haunted';

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

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

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

Ключевой момент: Haunted не создаёт виртуальный DOM. Вместо этого она использует template literals и lit-html-подобный рендеринг, что уменьшает накладные расходы на diffing и повышает производительность при больших приложениях.


Контекст компонентов

Haunted поддерживает контекст, который позволяет передавать данные через иерархию компонентов без необходимости передавать их явно через props. Контекст создаётся функцией createContext и используется через useContext и provideContext.

Создание контекста:

import { createContext, provideContext, useContext, component, html } from 'haunted';

const ThemeContext = createContext('light');

function ThemeProvider({ theme, children }) {
  provideContext(ThemeContext, theme);
  return html`<slot></slot>`;
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return html`<button class=${theme}>Кнопка с темой ${theme}</button>`;
}

customElements.define('theme-provider', component(ThemeProvider));
customElements.define('themed-button', component(ThemedButton));

Особенности производительности при работе с контекстом:

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

Оптимизация рендеринга

Haunted предоставляет несколько стратегий для повышения производительности:

  1. Мемоизация функций и значений Использование useMemo и useCallback помогает предотвратить ненужные пересоздания функций и вычислений при каждом ререндере.

    import { useMemo, html, component } from 'haunted';
    
    function ExpensiveComponent({ items }) {
      const sortedItems = useMemo(() => items.sort(), [items]);
      return html`<ul>${sortedItems.map(i => html`<li>${i}</li>`)}</ul>`;
    }
  2. Фрагментация DOM Haunted рендерит только изменённые узлы, что позволяет использовать вставку и удаление элементов без полной перерисовки родительского контейнера.

  3. Отложенный ререндеринг useEffect и useLayoutEffect позволяют выполнять операции после рендера и предотвращают блокировку основного потока, что критично при работе с тяжёлыми вычислениями или асинхронными запросами.


Контроль над жизненным циклом

Haunted предоставляет хуки для управления жизненным циклом компонента:

  • useEffect — выполняется после каждого рендера, можно задать зависимости для оптимизации.
  • useLayoutEffect — выполняется синхронно после изменения DOM, полезно для вычислений размеров и анимаций.
  • useReducer — для сложного состояния, которое зависит от предыдущего состояния, снижает количество лишних обновлений.

Пример использования useEffect для асинхронного запроса:

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

function DataFetcher({ url }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let canceled = false;
    fetch(url)
      .then(res => res.json())
      .then(json => { if (!canceled) setData(json); });
    return () => { canceled = true; };
  }, [url]);

  return html`<pre>${JSON.stringify(data, null, 2)}</pre>`;
}

customElements.define('data-fetcher', component(DataFetcher));

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


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

  • Разделять логику и рендеринг с помощью хуков.
  • Минимизировать вложенность DOM-дерева в компонентах, особенно при частых обновлениях состояния.
  • Использовать контекст только там, где это действительно нужно, чтобы избежать лишних пересчётов.
  • Для тяжёлых вычислений применять мемоизацию и отложенные эффекты.
  • Использовать slot для передачи структурного контента, что снижает количество пересоздаваемых элементов.

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