Description
- I searched for an existing RRFC which might be relevant to my RRFC
Motivation
Functions are the simplest unit of composition and the first tool a developer leverages to accomplish almost any task. lit
empowers developers to write shareable interoperable code that creates rich interactive interfaces. Minimizing the distance and friction between starting with a function and ending with a great UI serves that goal.
There are 2 main pieces of this puzzle: (1) describing rendering, (2) managing reactive state.
Describing rendering: Lit's html
and css
TTL syntaxes are excellent at this, and can easily be returned from a function.
Managing reactive state:
- Lit's reactive properties provide an excellent mechanism for managing reactive state and make sense to use when creating custom elements that extend
LitElement
. If a dev started with a function, they must move to a class, and this is fine when it makes sense. - However, the new @lit-labs/preact-signals library provides an alternative for simpler use cases and it more seamlessly builds on top of functions.
- Signals are reactive state containers. Their reactivity does require using them within a scope that records dependencies (aka
effects
), but the lit signals package providesSignalWatcher
andwatch
to deal with this. - The one remaining problem is having a convenient way to create signals within a function. They can certainly be passed in as arguments, but what if they are internal the ui being managed by the function?
- Signals are reactive state containers. Their reactivity does require using them within a scope that records dependencies (aka
This last issue is addressed below...
Example
Consider creating a simple counter:
In a LitElement, it looks like:
@state()
accessor count = 0;
render() {
return html`<button @click=${() => this.count++}>${this.count}</button>`;
}
With a function using signals, this can be:
const renderCounter = () => html`<button @click=${() => count.value++}>${count}</button>`;
But this doesn't work because we need to get the count
signal from somewhere.
How
React solves this problem with hooks, but these have tradeoffs. Lit can make this more straightforward by leveraging the fact that all Lit templates are rendered into persistent "parts," and access to them is provided via the directives API.
So, all we need is a simple directive that can initialize state. Here's the updated counter example:
const renderCounter = (use) => {
const count = use.state(() => signal(0));
return html`<button @click=${() => count.value++}>${count}</button>`;
}
Then use it like this:
return html`counter: ${stateful(renderCounter)}`
See working prototype.
The stateful
directive provides a state
method which memoizes the result of its argument.
References
- n/a