Signals: новая волна реактивности во фронтенде
1. Эволюция реактивных моделей
Суть фронтенд-разработки заключается в том, чтобы «отобразить состояние в пользовательский интерфейс». За многие годы мы стали свидетелями непрерывной эволюции реактивных моделей: от ручных операций с DOM через модели MVC/MVVM к виртуальному DOM (Virtual DOM), популяризированному React. Благодаря пакетной обработке изменений состояния и сравнению на уровне компонентов VDOM значительно упростил разработку интерфейсов.
Однако VDOM — не универсальное решение. Затраты на повторный рендеринг и сравнение на уровне компонентов в некоторых высокодинамичных сценариях могут стать узким местом производительности. В последние годы более старая парадигма, оптимизированная современными компиляторами, — мелкогранулярная реактивность (Fine-Grained Reactivity) — вновь оказалась в центре внимания, а её основным носителем стали Signals.
2. Что такое Signals?
Signal — это реактивный примитив (Reactive Primitive), который инкапсулирует значение и автоматически уведомляет все его зависимости при изменении этого значения. В отличие от VDOM-фреймворков, отслеживающих изменения на уровне компонентов, Signal автоматически создаёт граф зависимостей на уровне чтения данных.
При обновлении значения Signal повторно выполняются только те вычисления (Memo) или побочные эффекты (Effect), которые прямо или косвенно зависят от этого Signal. Так достигается точечное, «хирургическое» обновление DOM без необходимости сравнивать VDOM.
Обычно его основу составляют три примитива:
createSignal(value): Создаёт реактивную единицу состояния.createEffect(() => {}): Создаёт побочный эффект, который автоматически отслеживает зависимости и реагирует на изменения.createMemo(() => {}): Создаёт производное кэшируемое реактивное вычисление.
// SolidJS 语法示例
const [count, setCount] = createSignal(0);
const doubleCount = createMemo(() => count() * 2);
createEffect(() => {
console.log(`The double count is: ${doubleCount()}`);
});
setCount(1); // 这将触发 memo 重新计算,并触发 effect 重新执行Важная ментальная модель состоит в том, что во фреймворках на основе Signal, таких как SolidJS, сама функция компонента выполняется лишь один раз при инициализации. Все последующие обновления полностью управляются реактивной системой, а не повторным рендерингом компонента.
3. Широкое распространение в экосистеме
Signal — не совершенно новая концепция: её идеи восходят к ранним фреймворкам вроде Knockout.js. Однако современные компиляторы JavaScript дали ей новую жизнь, и сегодня её широко применяют крупные фреймворки:
- SolidJS & Qwik: Полностью построены на Signal и служат эталонными реализациями этой парадигмы.
- Preact: Добавил официальную поддержку через пакет
@preact/signals, который можно интегрировать в существующие проекты Preact/React. - Vue:
refиcomputedиз Composition API, по сути, представляют собой одну из реализаций Signal. - Svelte 5: Грядущее обновление «Runes» перестраивает реактивную модель Svelte, опираясь в своей основе на идеи и реализацию парадигмы Signal.
- Angular: В недавних версиях также появились Signals как новые реактивные примитивы.
Такое сближение разных фреймворков доказывает ценность Signal как эффективной и предсказуемой реактивной модели.
4. Преимущества и компромиссы
Преимущества:
- Выдающаяся производительность: Отказ от VDOM и повторного рендеринга на уровне компонентов обычно обеспечивает отличные результаты в тестах производительности.
- Предсказуемость: Поток обновлений состояния прозрачен, его легко понимать и отлаживать.
- Низкое потребление памяти: Не требуется хранить виртуальное представление всего дерева компонентов.
Компромиссы:
- Смена ментальной модели: Разработчикам, привыкшим к циклу рендеринга React, нужно освоить новую модель, в которой «компонент выполняется только один раз».
- Интеграция с экосистемой: Хотя Signals сами по себе обладают высокой производительностью, глубокая интеграция с обширной VDOM-экосистемой React — например, с некоторыми UI-библиотеками — может потребовать дополнительной адаптации.
Заключение
Возрождение Signals знаменует важный этап эволюции парадигм управления состоянием во фронтенде. Оно возвращает внимание разработчиков от вопроса «как рендерится компонент» к более фундаментальному вопросу «как движется состояние». Благодаря глубокой интеграции с компиляторами Signal обеспечивает отличный опыт разработки и одновременно открывает новые возможности на границах производительности Web-приложений.