Нативные возможности через Web APIs

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

Доступ к элементам через ref

Для прямого взаимодействия с DOM-узлами используется механизм ref, который передаёт ссылку на реальный элемент браузера:

import { useRef, useEffect } from 'preact/hooks';

function Example() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

ref.current содержит HTMLInputElement, что позволяет вызывать любые методы Web API: focus, scrollIntoView, getBoundingClientRect.

События как нативные Event

Система событий в Preact почти полностью совпадает с DOM Events. Обработчики получают стандартный объект Event, а не синтетическую обёртку:

function Clicker() {
  function handleClick(event) {
    console.log(event instanceof MouseEvent); // true
  }

  return <button onCl ick={handleClick}>OK</button>;
}

Это упрощает интеграцию с низкоуровневыми API браузера и сторонними библиотеками.


Fetch API и работа с сетью

Preact не содержит встроенных инструментов для HTTP-запросов. Используется стандартный fetch, доступный в браузере.

Асинхронная загрузка данных

import { useEffect, useState } from 'preact/hooks';

function DataLoader() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/items')
      .then(res => res.json())
      .then(setData);
  }, []);

  if (!data) return <span>Загрузка...</span>;

  return <ul>{data.map(i => <li>{i.name}</li>)}</ul>;
}

fetch возвращает Promise, а его поведение полностью соответствует спецификации WHATWG.

AbortController

Для отмены запросов применяется AbortController, особенно полезный при размонтировании компонента:

useEffect(() => {
  const controller = new AbortController();

  fetch('/api/items', { signal: controller.signal });

  return () => controller.abort();
}, []);

LocalStorage и SessionStorage

Хранилища браузера часто используются для кэширования данных и сохранения состояния.

Чтение и запись

function saveToken(token) {
  localStorage.setItem('auth_token', token);
}

function loadToken() {
  return localStorage.getItem('auth_token');
}

Работа с localStorage синхронная, поэтому доступ осуществляется напрямую, без дополнительных хуков.

Связь с состоянием компонента

const [theme, setTheme] = useState(
  () => localStorage.getItem('theme') || 'light'
);

useEffect(() => {
  localStorage.setItem('theme', theme);
}, [theme]);

Использование ленивой инициализации состояния предотвращает лишние обращения к API.


History API и управление навигацией

При разработке SPA часто требуется ручное управление историей браузера.

pushState и popstate

function navigate(path) {
  history.pushState({}, '', path);
  window.dispatchEvent(new Event('popstate'));
}

В сочетании с прослушиванием события popstate реализуется собственный роутинг без сторонних библиотек.

useEffect(() => {
  const handler = () => setPath(location.pathname);
  window.addEventListener('popstate', handler);
  return () => window.removeEventListener('popstate', handler);
}, []);

Intersection Observer

API наблюдения за видимостью элементов полезно для ленивой загрузки и анимаций.

Пример наблюдения

useEffect(() => {
  const observer = new IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        console.log('Элемент виден');
      }
    });
  });

  observer.observe(ref.current);

  return () => observer.disconnect();
}, []);

IntersectionObserver работает асинхронно и не блокирует основной поток.


Resize Observer

Позволяет отслеживать изменение размеров элементов без опроса (polling).

useEffect(() => {
  const ro = new ResizeObserver(entries => {
    for (const entry of entries) {
      console.log(entry.contentRect.width);
    }
  });

  ro.observe(ref.current);

  return () => ro.disconnect();
}, []);

Используется для адаптивных компонентов, графиков и сложных макетов.


Web Workers

Preact не ограничивает использование многопоточности браузера.

Создание воркера

const worker = new Worker('/worker.js');

worker.postMessage({ data: 42 });

worker.onmess age = e => {
  console.log(e.data);
};

Обработка сообщений может обновлять состояние компонентов без блокировки UI.


RequestAnimationFrame и анимации

Для высокопроизводительных анимаций применяется requestAnimationFrame.

useEffect(() => {
  let frameId;

  function animate() {
    setX(x => x + 1);
    frameId = requestAnimationFrame(animate);
  }

  frameId = requestAnimationFrame(animate);
  return () => cancelAnimationFrame(frameId);
}, []);

Взаимодействие с этим API напрямую позволяет избегать лишних абстракций и контролировать частоту обновлений.


Media APIs

Работа с matchMedia

const media = window.matchMedia('(prefers-color-scheme: dark)');

media.addEventListener('change', e => {
  console.log(e.matches ? 'dark' : 'light');
});

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

Audio и Video

const audio = new Audio('/sound.mp3');
audio.play();

Все мультимедийные возможности браузера доступны без дополнительных адаптеров.


Custom Elements и интеграция

Preact хорошо сочетается с Web Components.

customElements.define('my-widget', class extends HTMLElement {
  connectedCallback() {
    this.textContent = 'Hello';
  }
});

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

<my-widget />

Preact корректно рендерит кастомные элементы и передаёт им атрибуты.


Итоговая особенность подхода

Preact не заменяет Web APIs, а органично сосуществует с ними. Компоненты выступают в роли удобной структуры, внутри которой используются нативные возможности браузера без ограничений, прокси-объектов и искусственных слоёв абстракции. Такой подход упрощает отладку, уменьшает размер бандла и сохраняет предсказуемость поведения приложения.