やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

超シンプル軽量爆速リアクティブDOMライブラリ《VanJS》

 わずか1.8KBでReactみたいなことができちゃうスゴイ奴!

なぜVanJS

 公式Why VanJS ?にも書いてありますが、他の類似ライブラリと比較してシンプルで理解しやすく超軽量です。

  • シンプルなリアクティブ・プログラミング
  • 最小構成
  • 超軽量

シンプルなリアクティブ・プログラミング

リアクティブ・プログラミング

 リアクティブ・プログラミングとはデータ変更による伝播を関心事とするプログラミング手法です。

const date = van.state(0)
const week = van.derive(()=>Math.floor(date.val / 7))
const month = van.derive(()=>Math.floor(date.val / 30))
const year = van.derive(()=>Math.floor(date.val / 365))

date.val = 382
console.log(`date : ${date.val}`)  // 382
console.log(`week : ${week.val}`)  // 54
console.log(`month: ${month.val}`) // 12
console.log(`year : ${year.val}`)  // 1

See the Pen van-state-derive by ytyaru (@ytyaru) on CodePen.

 状態に値を代入したら、それを契機に処理が実行されます。モデル駆動に似ていますね。

 え、何がスゴイかわからない? コードをよ〜く見てみそ。date変数に値をセットしただけなのに、week, month, yearまで計算されてるでしょ? 関数も呼んでないし、イベント発火もしてないのに。これがリアクティブ・プログラミングって奴らしいですよ?

 注目すべきはDOM更新を想定している点です。次のCodePenのResultにある入力ボックスにURLを入れたら、同時にリンクが動的生成できます。コードもシンプル!

シンプル

 Reactなどの類似ライブラリは複雑です。所定のフレームワーク構造を覚えねばなりません。VanJSはたった3つのインタフェースさえ知っていれば使えます。

const {div,input,a} = van.tags
const url = van.state('')
van.add(document.body, div(
    input({oninput:e=>url.val=e.target.value}),
    a({href:()=>url.val}, ()=>url.val),
))

See the Pen Untitled by ytyaru (@ytyaru) on CodePen.

  • van.state()でリアクティブな変数を定義する
  • van.add()でDOM要素を挿入する
  • van.tagsでHTML要素作成関数を参照する

 上記コードではHTML要素div,input,aを作成しています。これらはJavaScript関数であり、次のようなフォーマットに従っています。

van.tags.HTML要素名({属性名:属性値/それを返す関数}, HTML要素/テキストノード値/それを返す関数)

 これはHTML書式とその順序にそっくりです。覚えやすいですね。

<HTML要素名 属性名="属性値">HTML要素/テキストノード値</HTML要素名>

 先述のコードからHTML要素作成箇所を抜き出してみると以下です。

div(
  input({oninput:e=>url.val=e.target.value}),
  a({href:()=>url.val}, ()=>url.val),
)

 これはHTMLで以下のように書いたのと同じです。

<div>
  <input oninput="setState()"></input>
  <a href=""></a>
</div>

 HTMLのonclick属性値ではJavaScriptの関数を呼び出しています。これはJavaScript定義箇所を見ないといけないので面倒ですよね。最初からDOMも処理も全部JavaScriptで書ければシンプルです。それが先述のコードです。

 もし全部を素のHTMLとJSで書いたら以下のようになります。

<script>
window.addEventListener('DOMContentLoaded', async(event) => {
function setState() {
    const v = document.querySelector('input').value
    const a = document.querySelector('a')
    a.setAttribute('href', v)
    a.innerText = v
}
})
</script>
<div>
  <input oninput="setState()"></input>
  <a href=""></a>
</div>

See the Pen vanilla-van-state-add by ytyaru (@ytyaru) on CodePen.

 冗長ですね。どうせほとんどJSで実装するのだから、HTMLもJSでDOM生成したほうが見やすいしDRYに書けます。VanJSなら、できます。

最小構成

 VanJSライブラリを参照するだけで使えます。

<script src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.8.nomodule.min.js"></script>

 もちろんローカルにダウンロードして参照しても使えます。

超軽量

 執筆時点(v1.2.8)では1.8KBでした。軽すぎ!

ライブラリ サイズ
VanJS 1.2.8 1.8KB
solid.js 1.8.12 8.1KB
jQuery 3.7.1 29.7KB
Vue.js 3.4.15 40KB
ReactDOM 18.2.0 42KB
Angular 17.1.0 104KB

 軽量であることは高速であることにもつながります。ライブラリのダウンロードだけでなく、処理数も少ないため高速です。

問題点

 リアクティブ・プログラミングには難点があります。それは状態をセットしたら同時にDOM更新されてしまう点です。え、それって利点じゃなかったの? と思うでしょう? 残念ながら、逆に厄介になる場合があります。

See the Pen Untitled by ytyaru (@ytyaru) on CodePen.

 入力ボックスに入れた値が、ボタンを押したら消えてしまいました……。テーブル全体は動的に追加したいけど、入力値はそのままにして欲しいの! そこは言わなくても分かってよ!
ヽ(`Д´)ノ ってなるんです。

  • テーブル要素+入力要素+リアクティブ=入力値がクリアされる

 いやいや、そんなのユーザ名も状態にセットすればいいだけっしょ(笑) そんなことも分からんとかバッカでーwww と思うでしょ? それやったらユーザ名を一文字入力するたびにフォーカスが消えてしまうんです……。

See the Pen van-reactive-probrem by ytyaru (@ytyaru) on CodePen.

 ならばフォーカスをもつユーザ名入力ボックスIDを保存する状態を作って、フォーカス設定する副作用を定義すればどうか。と思ったけど、最初の問題であるユーザ名クリア問題に戻った……。

See the Pen van-reactive-probrem-3 by ytyaru (@ytyaru) on CodePen.

 当然の結果です。結局はユーザ名のinputイベントで状態をセットしたら、その時点でリアクティブにHTML要素を更新してしまいます。なのでユーザ名を状態にしてセットすれば入力直後にHTML再更新されてフォーカスを失うし、フォーカスIDを状態にしてセットすれば入力直後にHTML再更新されて入力値を失います。残念ながら、このどちらか一方しかありえません。八方塞がりな状態です。リアクティブのジレンマ。たすけてorz

  • 入力要素+リアクティブ=入力値がクリアされるorフォーカス喪失

 上等だ! 俺ァまだ諦めねーぜ! こうなったらsetTimeout()使って無理やりフォーカス当ててやんよゴルァ! ようはユーザ名を状態にしてセットしつつも、その直前でsetTimeout()でフォーカス設定する処理を仕込めばいいだけだろ?
 リアクティブ? ハッ! もうそんなの、どーでもいいぜ! かまうこたぁねぇ、ぶっこわしてやらぁ! パラダイム・ブレイクだ! 俺様に不可能はねーぜ! ヒャハー!
 と思ったけどダメでした。フォーカスが当たりません。なぜ?

See the Pen van-reactive-probrem-3 by ytyaru (@ytyaru) on CodePen.

  • 入力要素+リアクティブ=入力値がクリアされるorフォーカス喪失

 リアクティブって怖いね……。これ致命的では? 解決方法ないの?

 リアクティブ・プログラミングって、チューリング完全じゃないのでは? チューリング還元という概念は関係ある?

 このリアクティブ特有の難しさを、だれか言語化してください。頭の悪い私には難しすぎて。「こういう場合はリアクティブ・プログラミングでは解決できないよ!」と事前に気づけるようになりたいんです。そうでないと書いた後でドツボにハマります。

 今回のコードはたぶんリアクティブ・プログラミングでは解決できません。状態を使わず、素のJSのイベントとDOM APIを使って解決するしかないのでしょう。

 良いことばかりの万能ライブラリなどありえません。公式VanJSみたく良いことしか書いてない記事には必ず落とし穴があります。ちゃ〜んと問題点までフォローして書いてあるとは、この記事ってば何て信頼できるスンバらしい記事なんでしょう! これはもう拡散必至ですよね?! ね?!

まとめ

 VanJSは名だたる大御所フレームワークと比較しても圧倒的に軽量かつシンプル! React等を使って「わかりにくいな〜」と思ったことがある人は、ぜひVanJSを使ってみてね。

 詳しくはVanJSの公式サイトを見ると分かりやすいです。英語だけど翻訳して英文と見比べながら読み進めれば何とかなります。

 リアクティブの罠にハマったら素のAPIを頼ることになるでしょう。薄いラッパーのほうがショックが小さく済むので、シンプルなVanJSは良き。