packages/lit-dev-content/site/blog/2024-10-08-signals.md
We’re thrilled to announce the release of our newest Lit Labs package,
@lit-labs/signals, which integrates the
polyfill for the
exciting new TC39 Signals Proposal
directly with Lit. This package provides a powerful, reactive way to manage
state in your web applications by allowing you to use shared, observable
signals that automatically update components when their values change.
Signals are quickly becoming a core feature in the JavaScript ecosystem, and the TC39 proposal has the potential to unify signals and how we manage state and reactivity across various libraries and frameworks.
Though the proposal is in its early stages, you can start experimenting with
signals and @lit-labs/signals today to see how building components and apps on
a universal reactivity primitive might work for you.
In simple terms, signals are observable data structures that hold values or computations. When the value of a signal changes, all components or parts of your app that depend on it are automatically notified and updated. This is particularly useful in UIs where multiple components might need to share and react to changes in state.
@lit-labs/signalsLit is already known for its lightweight, performant, and declarative approach to building web components. But Lit is tightly focused on helping you build reusable, encapsulated components. Lit is not a framework and does not precribe how to model your data or make it observable.
Lit's reactivity is by default relatively shallow. Components automatically update when their own reactive properties change, but not when nested properties change. Responding to deep property changes has either required manual update requests, or the integration of a state management system like Redux or MobX.
Signals give us many of the same deep observability abilities as these state management systems, but with a smaller, simpler API, and the potential to be a common standard across a large ecosystem of utilies, components, and frameworks.
Signals aren't entirely new to Lit. We previously released the
@lit-labs/preact-signals package, but we were somewhat unsatisfied with the
need to build a Lit integration for a specific signals library, and potentially
every signals library that Lit developers might want to use.
Standardized signals in JavaScript would let us build just one integration (and eventually add signals support directly in Lit's core), and enable interop between signal-using libraries in the same spirit of the interop that web components enable.
The new @lit-labs/signals package makes it super easy to use the new
signals proposal from within your Lit components.
Let’s dive into a few examples...
Here’s a simple example of a shared counter using @lit-labs/signals. To enable
signal-based reactivity in this component, we just use the SignalWatcher mixin
in our Custom Element definition; any signals we read from will automatically be
observed, triggering updates whenever their values change:
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
import {SignalWatcher, signal} from '@lit-labs/signals';
// This is a standard TC39 signal that uses the signals polyfill.
// The signal is shared across all component instances.
const count = signal(0);
@customElement('shared-counter')
export class SharedCounterComponent extends SignalWatcher(LitElement) {
render() {
// Just by using the signal in your template, your component will update
// when the signal changes.
return html`
<p>The count is ${count.get()}</p>
<button @click=${this.increment}>Increment</button>
`;
}
increment() {
count.set(count.get() + 1);
}
}
With this approach, any number of <shared-counter> components can be added to
the DOM, and all will reflect the same counter value, automatically updating
when the signal changes.
You can also see this example in the Lit Playground.
Using the watch directive, we can also achieve pinpoint updates targeting
individual bindings rather than an entire component:
import {LitElement, html} from 'lit';
import {customElement} from 'lit/decorators.js';
import {SignalWatcher, watch, signal} from '@lit-labs/signals';
const count = signal(0);
@customElement('pinpoint-counter')
export class PinpointCounter extends SignalWatcher(LitElement) {
render() {
return html`
<p>The count is ${watch(count)}</p>
<button @click=${this.increment}>Increment</button>
`;
}
increment() {
count.set(count.get() + 1);
}
}
Here, only the binding displaying the count is processed when the signal changes, skipping unnecessary work and improving performance.
This is just the beginning of our exploration of signals integration for Lit.
Coming soon, we will add more utilities to @lit-labs/signals for incrementally
rendering changes from collections, easily running side-effects in components,
and using signals for reactive properties.
Eventually—as the proposed standard progresses and we gain experience using signals in Lit—signals will likely become part of the core library.
The Lit team is working closely with the TC39 Signals Proposal champions to ensure that signals work great for Lit, web components, and plain-DOM use cases. We can't overstate how much potential we see in signals to form an interoperable reactivity primitive that works across libraries and components without requiring a centralized framework.
We’re eager to get feedback from the community as you experiment with this new
package. As part of the Lit Labs family, @lit-labs/signals is still
experimental and may undergo significant changes based on user feedback and
advancements in the TC39 Signals Proposal.
To get started, simply install the package:
npm i @lit-labs/signals
We’d love to hear your thoughts, so please try it out, and let us know how it works for you.
We’re excited to see what you’ll build with signals and how they will shape the future of state management in web applications!
Thanks!
-The Lit Team