Signals в Fresh

Signals — это фундаментальный механизм реактивности во фреймворке Fresh, который позволяет отслеживать изменения состояния и автоматически обновлять связанные с ним компоненты. В отличие от традиционного управления состоянием через useState или контекстные API, signals обеспечивают более тонкую реактивность, минимизируя перерендеры и повышая производительность.

Создание Signals

Для создания signal используется функция signal из пакета @preact/signals:

import { signal } from "@preact/signals";

const counter = signal(0);

Здесь counter — это объект, который содержит текущее значение value и методы для его обновления. Значение signal хранится в свойстве value и может быть изменено напрямую:

counter.value += 1;

При изменении значения все компоненты, которые используют этот signal, автоматически перерендериваются.

Чтение и использование Signals в компонентах

Signals можно использовать внутри Preact-компонентов как обычные переменные. При рендеринге значение signal.value отслеживается системой реактивности:

function Counter() {
  return (
    <div>
      <p>Счетчик: {counter.value}</p>
      <button onCl ick={() => counter.value++}>Увеличить</button>
    </div>
  );
}

Важно понимать, что signal автоматически подписывается на изменения. Компонент обновится только если изменится конкретный signal, используемый в рендере. Это уменьшает количество ненужных перерендеров, в отличие от глобального состояния.

Композиция Signals

Signals можно комбинировать и использовать для создания вычисляемых значений. Для этого применяются функции computed и производные signals:

import { computed } from "@preact/signals";

const doubleCounter = computed(() => counter.value * 2);

doubleCounter автоматически обновляется при изменении counter.value. Это позволяет создавать реактивные зависимости без ручного написания эффекта useEffect.

Реактивные эффекты

Для выполнения побочных эффектов при изменении signal используется функция effect:

import { effect } from "@preact/signals";

effect(() => {
  console.log(`Новое значение счетчика: ${counter.value}`);
});

Эффект будет вызван каждый раз, когда изменится counter.value. Это особенно полезно для синхронизации состояния с внешними API или локальным хранилищем.

Signals в Fresh и серверный рендеринг

Fresh использует signals для оптимизации рендеринга на сервере и клиенте. При SSR signal инициализируется на сервере и передается на клиент без потери состояния. Это позволяет:

  • Избегать гидратации всего дерева компонентов.
  • Минимизировать объем передаваемых данных.
  • Сохранять реактивность сразу после загрузки страницы.

Пример интеграции signals в Fresh-странице:

import { signal } from "@preact/signals";
import { HandlerContext } from "$fresh/server.ts";

const likes = signal(0);

export default function Page() {
  return (
    <div>
      <h1>Лайки: {likes.value}</h1>
      <button onCl ick={() => likes.value++}>Поставить лайк</button>
    </div>
  );
}

Состояние likes будет синхронизировано между серверным рендером и клиентским обновлением.

Взаимодействие Signals с формами и событиями

Signals идеально подходят для управления формами и пользовательскими событиями. Вместо сложной структуры useState можно использовать signals для каждого поля формы:

const username = signal("");
const password = signal("");

function handleSubmit() {
  console.log(username.value, password.value);
}

Изменение значения input напрямую обновляет signal:

<input
  type="text"
  value={username.value}
  onIn put={(e) => (username.value = e.currentTarget.value)}
/>

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

  • Тонкая реактивность — обновляются только компоненты, которые используют конкретный signal.
  • Простота и лаконичность — меньше шаблонного кода по сравнению с useState и useEffect.
  • Согласованность SSR и CSR — состояния сохраняются между серверным рендером и клиентским гидрированием.
  • Вычисляемые значения и эффекты — легкая композиция сигналов без необходимости создавать лишние хранилища состояния.

Ограничения и особенности

  • Signals лучше всего подходят для локального состояния и небольших реактивных цепочек. Для глобального состояния или сложных структур стоит комбинировать их с контекстом или специализированными хранилищами.
  • Использование сигналов в чисто серверных функциях без передачи на клиент не имеет смысла, так как реактивность реализуется только на клиенте.
  • Не рекомендуется напрямую мутировать вложенные объекты внутри signal. Лучше создавать новый объект и присваивать его value, чтобы гарантировать корректное отслеживание изменений.

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