Сигналы¶
Этот пакет входит в семейство экспериментальных пакетов Lit Labs. Как использовать пакеты Labs в продакшене, описано на странице Lit Labs.
Обзор¶
Что такое сигналы?¶
Сигналы — это структуры данных для наблюдаемого состояния.
Сигнал хранит либо одно значение, либо вычисляемое значение, которое зависит от других сигналов. Сигнал можно наблюдать: потребитель узнаёт, когда значение изменилось. Вычисляемые сигналы образуют граф зависимостей, пересчитываются и уведомляют потребителей, когда меняются их зависимости.
Сигналы удобны для общего наблюдаемого состояния — состояния, которое читают и меняют разные компоненты. Когда сигнал обновляется, обновляется каждый компонент, который наблюдает этот сигнал или любой сигнал, зависящий от него.
Сигналы — общая идея. В библиотеках и фреймворках JavaScript есть много реализаций. Сейчас в TC39 идёт предложение стандартизировать сигналы как часть JavaScript.
В API сигналов обычно три понятия:
- Сигналы состояния хранят одно значение.
- Вычисляемые сигналы оборачивают вычисление, которое может зависеть от других сигналов.
- Наблюдатели, или эффекты, запускают код с побочными эффектами, когда значения сигналов меняются.
Пример¶
Пример на предлагаемом стандартном API сигналов JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
Библиотеки сигналов¶
На JavaScript написано много реализаций сигналов. Часть из них встроена во фреймворки и доступна только из них, часть — отдельные библиотеки, которые можно вызывать из любого кода.
Конкретные API отличаются, но устроены похоже.
Библиотека сигналов Preact, @preact/signals, — отдельный пакет, относительно быстрый и небольшой. Первая интеграция Lit Labs была построена вокруг неё: @lit-labs/preact-signals.
Предложение сигналов для JavaScript¶
API сигналов похожи друг на друга, фреймворки всё чаще строят на них реактивность, и системам нужно взаимодействовать. Поэтому в TC39 идёт стандартизация: https://github.com/tc39/proposal-signals.
Пакет @lit-labs/signals интегрирует Lit с официальным полифилом этого предложения.
Для экосистемы веб-компонентов это важно. Если библиотеки и фреймворки примут стандарт, их сигналы будут совместимы: разным веб-компонентам не нужна одна и та же библиотека, чтобы читать и создавать сигналы.
Сигналы могут стать основой самых разных систем управления состоянием и библиотек наблюдаемости, новых и уже существующих. Сейчас MobX, Redux и похожие библиотеки требуют отдельный адаптер, чтобы удобно встроиться в жизненный цикл Lit. После стандартизации может хватить одного адаптера Lit, а когда поддержка сигналов войдёт в ядро Lit — и его не понадобится.
Сигналы и Lit¶
Сейчас есть два пакета интеграции:
@lit-labs/signals— предложение сигналов TC39.@lit-labs/preact-signals— сигналы Preact.
Предложение TC39 задумано как тот API, к которому сойдутся системы на JavaScript, поэтому в этом документе разбирается именно оно.
Установка¶
Установите @lit-labs/signals из npm:
1 | |
Использование¶
Из @lit-labs/signals нужны три вещи:
- Миксин
SignalWatcherдля классов, которые читают сигналы. - Директива шаблона
watch(), чтобы следить за отдельными сигналами и обновлять точечно. - Тег шаблона
html, который сам применяетwatch()к привязкам.
Импорт:
1 | |
@lit-labs/signals также экспортирует часть API полифила сигналов и фабрику тега withWatch(), чтобы к собственному тегу шаблона можно было добавить наблюдение за сигналами.
Автоматическое наблюдение через SignalWatcher¶
Проще всего применить миксин SignalWatcher к классу пользовательского элемента. После этого сигналы можно читать в методах жизненного цикла Lit, например в render(): изменение этих сигналов само запустит обновление. Записывать сигналы можно там, где это уместно, например в обработчиках событий.
В примере SharedCounterComponent читает и записывает общий сигнал. Все экземпляры показывают одно значение и обновляются, когда оно меняется.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
1 2 3 | |
Точечные обновления через watch()¶
Сигналами можно обновлять отдельные привязки, а не весь компонент. Для этого за сигналом следят директивой watch().
Обновления от watch() собираются в пакет и всё равно проходят реактивный жизненный цикл Lit. Если конкретное обновление Lit запустили только директивы watch(), пересчитываются лишь привязки с изменившимися сигналами. Остальные привязки шаблона пропускаются.
Тот же пример, но при изменении сигнала count обновляется только привязка ${watch(count)}:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Выигрыш от такого точечного обновления обычно невелик: пропускаются проверка идентичности шаблона из render() и проверка значения привязки @click. Обе операции дешёвые.
В большинстве случаев watch() не даёт заметного ускорения по сравнению с обычным рендером шаблона Lit. Lit и так обновляет в DOM только те привязки, значения которых изменились.
Экономия watch() растёт вместе с объёмом логики шаблона и числом привязок, которые можно пропустить. В шаблонах с большим количеством логики и привязок разница заметнее.
В @lit-labs/signals пока нет директивы repeat(), которая понимает сигналы. Пока изменения содержимого массивов приводят к полному рендеру.
Точечные обновления тегом html из пакета сигналов¶
@lit-labs/signals экспортирует особую версию тега html: она сама применяет watch() к любому сигналу в привязке.
Так не нужно писать директиву watch() или вызывать signal.get() там, где watch() нет.
Если импортировать html из @lit-labs/signals, а не из lit, привязки начнут следить за сигналами сами:
1 2 3 4 5 6 7 8 9 10 | |
Тег html из пакета сигналов пока плохо работает с lit-analyzer. Анализатор сообщает об ошибке типа, потому что видит присваивание Signal<T> туда, где ожидается T.
Одна копия полифила¶
@lit-labs/signals зависит от пакета signal-polyfill, отдельно его ставить не нужно.
Сигналы опираются на общую глобальную структуру — граф зависимостей. На странице или в приложении должна быть ровно одна копия полифила.
Если копий несколько (несовместимые версии или другие сбои npm), граф может разделиться: часть наблюдателей не увидит часть сигналов, а часть сигналов не попадёт в зависимости других.
Проверьте, что signal-polyfill установлен один раз:
1 | |
Если в списке больше одной строки signal-polyfill и рядом нет пометки deduped, в дереве лежат дубликаты.
Обычно это лечится так:
1 | |
Если не помогло, обновите зависимости, пока во всём дереве не останется одна совместимая версия signal-polyfill.
Чего пока нет¶
@lit-labs/signals ещё не полон. Планируются возможности, которые сделают работу с сигналами в Lit удобнее и быстрее:
- [ ] Директива
repeat(), понимающая сигналы. Инкрементальные обновления массивов станут эффективнее. - [ ] Декоратор
@property(), который хранит значение в сигнале и объединяет реактивные свойства с сигналами. Тогда общие утилиты сигналов проще применять к реактивным свойствам Lit. - [ ] Декоратор
@computed()для методов-вычисляемых сигналов. Вычисляемые сигналы мемоизируются, это помогает с дорогими расчётами. - [ ] Декоратор
@effect()для методов-эффектов. Это удобнее, чем отдельная утилита.
Полезные материалы¶
signal-utils¶
Пакет signal-utils содержит утилиты для предложения сигналов TC39:
- Наблюдаемые коллекции на сигналах:
Array,Map,Set,WeakMap,WeakSetиObject. - Декораторы для классов с полями на сигналах.
- Эффекты и реакции.
Коллекции и декораторы нужны, когда модель данных сложнее примитива.
Коллекции¶
Наблюдаемый массив:
1 2 3 | |
Чтение массива — обход или свойство .length — учитывается как доступ к сигналу. Мутации вроде .push() и .pop() уведомляют наблюдателей.
Декораторы¶
Декораторы описывают класс с наблюдаемыми полями, похоже на LitElement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
Классы с SignalWatcher, которые читают экземпляр GameState, будут за ним следить и обновляться, когда состояние игры меняется.
Статус и обратная связь¶
Пакет входит в экспериментальное семейство Lit Labs и активно разрабатывается. Могут отсутствовать возможности, встречаться серьёзные ошибки, а ломающие изменения случаются чаще, чем в основных библиотеках Lit.
Пакет также зависит от предложения и полифила, которые сами ещё не стабильны. По мере движения предложения API может меняться, и эти изменения попадут в полифил.
Пакет можно пробовать, чтобы накопить опыт и оставить отзыв об интеграции с Lit. Зависимости стоит фиксировать внимательно и проверять обновления, чтобы неожиданные ломающие изменения не застали врасплох.
Отзывы об интеграции — в обсуждении @lit-labs/signals. Ошибки можно завести в репозитории Lit.
Отзывы о предложении сигналов — в репозитории предложения. Ошибки полифила — здесь.