Skip to content

Instantly share code, notes, and snippets.

@wtnabe
Last active May 30, 2026 10:02
Show Gist options
  • Select an option

  • Save wtnabe/a1fffb4926a6e7b47a92a40c4598f61c to your computer and use it in GitHub Desktop.

Select an option

Save wtnabe/a1fffb4926a6e7b47a92a40c4598f61c to your computer and use it in GitHub Desktop.
Alien Signals x LitElement x Custom Model
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>alien-signals x LitElement x custom model</title>
</head>
<body>
<app-selector></app-selector>
<script type="importmap">
{
"imports": {
"lit": "https://esm.sh/lit",
"alien-signals": "https://esm.sh/alien-signals"
}
}
</script>
<script type="module">
import { LitElement, html } from 'lit'
import { signal, effect } from 'alien-signals'
/**
* @typedef {Object} Item
* @property {string} value
* @property {string} label
* @property {string} description
*/
/**
* @typedef {Object} Model
* @property {Item[]} items
* @property {Signal} selection
* @property {Function} pickAndSetItem
*/
class Model {
items = [
{ value: 'apple', label: 'Apple 🍎', description: '赤い果物' },
{ value: 'banana', label: 'Banana 🍌', description: '黄色い果物' },
{ value: 'cherry', label: 'Cherry 🍒', description: '小さな赤い果物' },
]
selection = signal(null)
pickAndSetItem = (e) => {
this.selection(e.target.value || null)
}
}
class AppSelector extends LitElement {
static properties = {
currentItem: { attribute: false }
}
connectedCallback() {
super.connectedCallback();
this.model = new Model()
this.stop ||= effect(() => {
this.currentItem = this.model.items.find((i) => i.value === this.model.selection()) ?? null
})
}
disconnectedCallback() {
super.disconnectedCallback();
this.stop?.()
this.stop = null
}
render() {
return html`
<item-selector
.items=${this.model.items}
.changeHandler=${this.model.pickAndSetItem}
></item-selector>
<display-component .item=${this.currentItem}></display-component>
`
}
}
customElements.define('app-selector', AppSelector)
class ItemSelector extends LitElement {
static properties = {
/** @type {Item} */
items: { type: Array, attribute: false },
/** @type {Function} */
changeHandler: { attribute: false }
}
render() {
return html`
<label>
アイテムを選択:
<select @change=${this.changeHandler}>
<option value="">-- 選択してください --</option>
${this.items.map((item) => html`<option value=${item.value}>${item.label}</option>`)}
</select>
</label>
`
}
}
customElements.define('item-selector', ItemSelector)
class DisplayComponent extends LitElement {
static properties = {
/** @type {Item | null} item */
item: { attribute: false }
}
render() {
return html`
<div>
<p>Selected: <strong>${this.item?.label ?? '(none)'}</strong></p>
${this.item ? html`<p>${this.item.description}</p>` : ''}
</div>
`
}
}
customElements.define('display-component', DisplayComponent)
</script>
</body>
</html>
@wtnabe

wtnabe commented May 28, 2026

Copy link
Copy Markdown
Author

メモ

設計について

  • Signals は必要な DOM だけ更新するための細粒度の reactivity ( fine-grained reactivity ) の考え方だが、素朴にそれをやるとどの Element で何が起きるのかの管理が煩雑になるのでここでは root element で全体を管理 ( Controller の役割 )
  • かつ、どの signal に対して副作用を与えるかもまとめて Model に閉じ込める
  • LitElement で管理しているので root element で signal も副作用もまとめてしまっても描画については Lit が最適化してくれる

alien-signalsについて

  • alien-signals は TC39 とは異なり、set(), get() ではなく、引数を与えるか与えないかで挙動が変わる

Notes

Design

  • Signals are based on the concept of fine-grained reactivity to update only the necessary DOM. However, implementing this naively leads to complex management of which elements are affected by what. Therefore, here the root element handles overall management (acting as a Controller).
  • Furthermore, the signals that trigger side effects are encapsulated within the Model.
  • Since management is handled by LitElement, Lit optimizes the rendering even if signals and side effects are consolidated at the root element level.

About alian-signals

  • Unlike TC39, alien-signals does not use set() or get(); its behavior changes depending on whether an argument is provided or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment