🍦

VanJSに新たな可能性を感じた話

2023/08/20に公開

VanJSとは

VanJS is an ultra-lightweight, zero-dependency, and unopinionated Reactive UI framework based on pure vanilla JavaScript and DOM.
VanJS

VanJSは、超軽量・依存関係なし・固定観念のないリアクティブUIフレームワークらしいです。例を見た感じ、今までの手続き型ではなく、ReactやVueのような宣言的な書き方で書けるようになります。
また仮想DOMやトランスパイルも必要とせず、単純なJavaScript関数とDOMで構築されています。

なぜVanJSなのか

公式では以下のページにVanJSを開発するに至った経緯が書かれています。
https://vanjs.org/about

自分なりにまとめてみました。こういった背景を知ることで、VanJSの良さがわかりますね。
間違った解釈等ありましたら、ご指摘いただけると幸いです。

Reactなど洗練されたUIフレームワークは便利だが、依存関係による不可解な問題の直面、指定されたスタイルでのプログラミング強制など、参入障害になることが懸念される。
VanJSはJavaScriptに基づき、Webサイトだけでなく、ほとんどの主要なOSがサポートするWebビューも含め、できるだけ多くの環境で動作する。特定開発での問題にとらわれない誰でも使用できることを目指している。

使用するバージョン

vanjs-core: 1.2.1

導入

CDNから読み込むだけで使えます。ReactやVueのように、コンパイルやビルドが必要ないので、すぐに使えます。

<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します。

import van from 'vanjs-core'

使い方

VanJSは簡単に使用できます。これだけ押さえておけば大丈夫です。

van.state()

van.state()は、状態を管理するための関数です。引数に初期値を渡します。ReactでいうuseState()と同じような感じですね。

const count = van.state(0)

値を取得するには、count.valを使用します。

console.log(count.val) // 0

値を更新するには、count.valに値を代入します。

count.val = 1
console.log(count.val) // 1

van.derive()

van.derive()は、状態を派生させるための関数です。引数に関数を渡します。ReactでいうuseMemo()と同じような感じですね。

const count = van.state(0)
const doubleCount = van.derive(() => count.val * 2)

van.add()

van.add()は、DOMを追加するための関数です。引数に追加したいDOMを渡します。

const app = document.getElementById('app')
if (app) {
  van.add(app, 'Hello World!')
}

van.tags

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())
})()

便利なコード変換が用意されてた

このページで変換後のコードを見ることができます。
https://vanjs.org/convert

HTMLからVanJSではどのように書くのか変換してくれます。慣れないうちはこちらで変換してみるといいでしょう。

さいごに

VanJSを使用することで、わかりやすくシンプルなコードを書くことができました。特に処理が複雑になるほど、VanJSのメリットが大きくなります。また、依存関係がないので依存由来の不具合が起きないのも良いですね。

個人的な使い所としては、ReactやVueを導入するほどでもないが、従来のDOM操作では大変な状態の場合に導入すると良いと考えます。

参考

Discussion