Open4
お手軽! DOMライブラリ制作記録
Litのタグ付きテンプレートリテラルとReactの関数型コンポーネントを組み合わせた、ノンディレクティブでステートフルなDOMマニピュレータのテンプレートライブラリを書いてみる
完成予想図
import { html, $, on } from "@mylibrary"
function App() {
const count = $(0);
return html`
<h1>${count}</h1>
<button ${{ [on.click]: () => $[count]++ }}>
Increment
</button>
`
}
write(document.body, App());
ぱっと見はReactだがここにJSXやHooksは登場せず、関数そのものは初回レンダリング時のみ実行され、値の更新は後に紹介する「ポインタ」が担う
テンプレート関数であるhtml
の構造は(...x) => x
といったシンプルなものであり、入力を解析する仕組みは持たない
このライブラリの最大の特長はSymbolを拡張した特殊な「ポインタ」オブジェクトを用いて、宣言的かつ高効率なデータフローを実現している点である
const count = $(0);
$[count] // 0
typeof count // "object"
$[count.toString()] // 0
関数$
はtoString()
でsymbolを返却する特殊なオブジェクトを提供する
そのため、count
ポインタにリスナの取り付けやSymbol.dispose
プロパティの付与を行える
valueプロパティにポインタを埋め込むだけで、簡単に双方向データバインディングを実装できる
双方向データバインディングには配列を使用する
値は常に0番地に代入される
function Input() {
const input = $("")
return html`
<h1>${input}</h1>
<input ${{ [at.type]: "text", value: input }} />
`
}
配列の1番地には代入時の副作用を記述できる
第1引数にはStrixInputEvent
が、unsafeでは生の HTMLInputEvent
が充てられる
要素のイベントには子要素の値の変更で発火される StrixEffectEvent
という独自実装が存在する
イベントリスナ属性では @effect
で使用できる
const OnEffectElement = () => {
let count = 0;
return () => html`
<label @effect=${({ oldValue }) => console.log(`changed from ${oldValue}`)}>
${count}
</label>
`
}
useState
的な振る舞いも可能
const LikeUseState = ({ $ }) => {
const count = $(0); // 動的な値
return () => html`
<h1>${$[count]}</h1>
<button @click=${() => $[count]++}>
Increment
</button>
`
}
$
は組み込みのポインタ関数であり、独立した実装は以下のリポジトリにある