VanJSで始める軽量リアクティブUI
VanJSは、依存関係ゼロの超軽量リアクティブUIフレームワークです。仮想DOMやトランスパイル不要で、純粋なJavaScriptとDOMだけで構築されています。
公式サイト: https://vanjs.org
VanJSとは
VanJS is an ultra-lightweight, zero-dependency, and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM.
主な特徴:
- 超軽量: 依存関係なし
- 宣言的: ReactやVueのような記述が可能
- ビルド不要: トランスパイルや仮想DOM不要
なぜVanJSなのか
公式の開発経緯: https://vanjs.org/about
ReactなどのUIフレームワークは便利ですが、以下の課題があります。
- 依存関係による不可解な問題
- 特定のスタイルでのプログラミング強制
- 学習コストの高さ
VanJSは、純粋なJavaScriptをベースとし、Webサイトやモバイルアプリ(WebView)など多くの環境で動作します。特定の開発手法に縛られない、誰でも使えるフレームワークを目指しています。
環境
vanjs-core: 1.2.1
導入
CDN経由
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.2.1.nomodule.min.js"></script>
npm経由
npm install vanjs-core@1.2.1
import van from 'vanjs-core'
基本的な使い方
van.state() - 状態管理
ReactのuseState()に相当します。
const count = van.state(0)
// 値の取得
console.log(count.val) // 0
// 値の更新
count.val = 1
console.log(count.val) // 1
van.derive() - 派生状態
ReactのuseMemo()に相当します。
const count = van.state(0)
const doubleCount = van.derive(() => count.val * 2)
van.add() - DOM追加
const app = document.getElementById('app')
if (app) {
van.add(app, 'Hello World!')
}
van.tags - HTMLタグ生成
const { div, button, p } = van.tags
例を見てみよう
公式の例文を元に、VanJSを触ってみましょう。
ボタンのカウントアップ
const { div, button, p } = van.tags
const Counter = () => {
const counter = van.state(0)
return div(
button({ type: 'button', onclick: () => ++counter.val }, '👍'),
p(counter)
)
}
van.add(document.body, Counter())
TODOリスト
ここは、公式の例文を元に、少し改変しています。
手続き型だと、DOM操作が長くなり処理も複雑になるのに対し、VanJSでは宣言的に書けるので、処理がシンプルになります。
const { div, button, p, ul, li, input } = van.tags
const TodoList = () => {
const todos = van.state<string[]>([])
const todo = van.state("")
return div(
div(
input({
type: 'text',
value: todo,
oninput: (e) => todo.val = e.target.value
}),
button({
type: 'button',
onclick: () => {
todos.val = [...todos.val, todo.val]
todo.val = ""
}
}, '追加')
),
() => ul(
todos.val.map((todo, index) => li(
todo,
button({
type: 'button',
onclick: () => todos.val = todos.val.filter((_, i) => i !== index)
}, '削除')
))
)
)
}
取得したデータの表示
次に、外部APIからデータを取得して表示する処理を行います。
const { ul, li, div } = van.tags
type Posts = {
body: string
id: number
title: string
userId: number
}[]
const App = async () => {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts').then(r => r.json()) as Posts
return (
div(
ul(
posts.map(post => li(post.title))
)
)
)
}
;(async () => {
van.add(document.body, await App())
})()
キーワード検索してみたり
van.derive()を使用することで、ステート更新をトリガーにして、新たなステートを生成できます。
表示部分で動的に変更させるために、関数で返すようにしています。
const { ul, li, div, label, input } = van.tags
type Posts = {
body: string
id: number
title: string
userId: number
}[]
const App = async () => {
const posts = await fetch('https://jsonplaceholder.typicode.com/posts').then(r => r.json()) as Posts
const keyword = van.state("")
const filteredPosts = van.derive(() => posts.filter(post => post.title.includes(keyword.val)))
return (
div(
div(
label({ for: 'keyword' }, 'キーワード'),
input({
type: 'text',
id: 'keyword',
oninput: (e) => keyword.val = e.target.value
})
),
// そのままmap展開しても動的に変わらない
// 関数で返すようにすると動的に変わる
() =>
ul(
filteredPosts.val.map(post => li(post.title))
)
)
)
}
;(async () => {
van.add(app, await App())
})()
便利なコード変換が用意されてた
このページで変換後のコードを見ることができます。
HTMLからVanJSではどのように書くのか変換してくれます。慣れないうちはこちらで変換してみるといいでしょう。
さいごに
VanJSを使用することで、わかりやすくシンプルなコードを書くことができました。特に処理が複雑になるほど、VanJSのメリットが大きくなります。また、依存関係がないので依存由来の不具合が起きないのも良いですね。
個人的な使い所としては、ReactやVueを導入するほどでもないが、従来のDOM操作では大変な状態の場合に導入すると良いと考えます。
Discussion