BA Barış Akın~/blog

·3 分で読めます·47 閲覧

zap コンポーネント:シグナルで動く小さな UI キット

3.7 KB のコア、依存関係ゼロ、57 個のコンポーネント。zap がシグナルでどう動くのか、コンポーネントをどう使うのかを簡単に紹介します。すべてのコンポーネントは deck.kartex.net でサンプル付きで動かせます。

zap コンポーネント:シグナルで動く小さな UI キット

このブログにある小さなインタラクティブ部品(言語切り替え、コードビューア、検索パレット)は zap で書かれています。zap はシグナルをベースにした、依存関係ゼロの小さな JavaScript UI ライブラリであり、それに付属するコンポーネントキットでもあります。ここでコンポーネントを一つずつ紹介することはしません。すべてのコンポーネントは、サンプルとコード付きで deck.kartex.net で実際に動かせます。この記事では、技術の仕組みと使い方を簡単にまとめるだけにとどめます。

deck.kartex.net:フィードバックカードとそれを組み立てるコード

zap の概要

  • 3.7 KB のコア(gzip)。仮想 DOM なし、コンパイラなし、必須のビルドステップもありません。
  • 依存関係ゼロ。 ブラウザ上で素の ES モジュールとして動きます。
  • 57 個のコンポーネント:フォームと入力、オーバーレイとフィードバック、ナビゲーション、表示、データグリッド、チャート、メディア。
  • SSR とハイドレーション:同じコンポーネントがサーバー上で HTML にレンダリングされ、ブラウザ上で動き出します。deck サイトの 65 ページはこの方法でプリレンダリングされています。
  • テーマ:CSS 変数(--zap-*)で、Default、Emerald、Violet、Rosé、macOS Aqua の各テーマを用意しており、それぞれにダークモードがあります。
  • アクセシビリティ:すべてのコンポーネントでロール、aria 属性、フォーカス管理、キーボード操作に対応しています。prefers-reduced-motion も尊重します。

シグナル

zap の中心にあるのはシグナルです。シグナルは値を保持する関数で、それを読み取るすべての場所が購読し、値が変わるとその場所だけが更新されます。コンポーネント全体が再描画されることはありません。

import { signal, computed, effect } from "@barisakin/zap";

const count = signal(0);
const double = computed(() => count() * 2);

effect(() => console.log("count", count(), "double", double()));

count.set(1);              // the effect runs again
count.update(n => n + 1);  // 2

untrack は読み取りを購読の対象から外し、onCleanup はエフェクトが再実行される前やコンポーネントが破棄される前に後片付けを行い、store はネストしたオブジェクトに対してフィールド単位のリアクティビティを提供します。

html テンプレート

ビューは JavaScript 標準のタグ付きテンプレートリテラルで書きます。テンプレートに関数として渡したもの(シグナルや () => ...)はすべてライブになり、そのテキストノードや属性だけが更新されます。

import { signal, html, mount } from "@barisakin/zap";

function Counter() {
  const n = signal(0);
  return html`
    <button onclick=${() => n.update(x => x + 1)}>
      clicked ${n} times
    </button>
  `;
}

mount(Counter, document.getElementById("app"));

when と each は条件付きの内容と繰り返しの内容を扱い、bind=${signal} は入力欄とシグナルを双方向に結び付けます。コンポーネントは (props) => Node という形の関数にすぎません。クラスも、ライフサイクルメソッドも、特別なファイル形式もありません。

コンポーネントの使い方

各コンポーネントはそれぞれ独立したモジュールになっているので、使うものだけを読み込めます。props には普通の値でもシグナルでも渡せます。シグナルを渡すと、コンポーネントはそれを読み取りもし、書き込みもします。

import { signal, html } from "@barisakin/zap";
import { Card } from "@barisakin/zap/components/card.js";
import { Rating } from "@barisakin/zap/components/rating.js";
import { Button } from "@barisakin/zap/components/button.js";
import { toast } from "@barisakin/zap/components/toast.js";

const stars = signal(0);

Card({
  title: "How was your order?",
  children: html`${Rating({ value: stars })}`,
  footer: Button({
    label: "Send",
    disabled: () => stars() === 0,
    onClick: () => toast.success("Thank you!"),
  }),
});

ダイアログも同じ仕組みです。open はシグナルで、true にするとダイアログが開き、ダイアログを閉じるとシグナルは false に戻ります。

import { Modal } from "@barisakin/zap/components/modal.js";

const open = signal(false);
Modal({ open, title: "Delete it?", children: "This cannot be undone." });
open.set(true);

ドロップダウン、ツールチップ、ポップオーバー、モーダルは二つの共通レイヤー(floating.js と overlay.js)を共有しているため、位置決め、フォーカストラップ、Escape での閉じる操作、スクロールロックは一か所で解決されています。

カンバン:ドラッグ&ドロップとキーボードでの移動

データとチャート

重めのものとしては、データグリッド(仮想スクロール、ソート、フィルタリング、グルーピング、編集)、スケジューラ、カンバンボード、ツリー、チャートがあります。チャートに渡すデータはシグナルにでき、それを変更するとチャートは新しい値へアニメーションしながら移ります。

import { ColumnChart } from "@barisakin/zap/components/chart.js";

const data = signal([12, 19, 7, 24]);
ColumnChart({ labels: ["Q1", "Q2", "Q3", "Q4"], series: [{ name: "Sales", data }] });

チャート:折れ線、面、棒

ルーター、i18n、SSR

  • ルーター:ページを再読み込みせずに遷移するための小さなルーター(Router、Link、navigate)。
  • i18n:翻訳には t()、言語の切り替えには setLocale() を使います。コンポーネント自身の文言は、トルコ語、英語、ドイツ語、フランス語、スペイン語、モンテネグロ語で最初から用意されています。
  • SSR:@barisakin/zap/ssr の renderToString は、同じコンポーネントをサーバー上で HTML に変換します。ブラウザ側では hydrate が既存の DOM を再利用し、イベントを結び付けます。
  • TypeScript:型定義(index.d.ts)がパッケージに同梱されています。

インストール

npm の場合:

npm install @barisakin/zap

あるいは、ツールを一切使わずに CDN から直接読み込むこともできます:

<link rel="stylesheet" href="https://zapjs.baltavista.com/theme.css">
<script type="module">
  import { signal, html, mount } from "https://zapjs.baltavista.com/zap.min.js";
</script>

すべてを見る

すべてのコンポーネントが、ライブサンプル、コピーできるコード、テーマとダークモードの切り替え付きで deck.kartex.net にあります。あるコンポーネントの使い方を知りたいなら、そこを見るのが一番の近道です。左のメニューから選び、サンプルを試し、コードをコピーしてください。

BA
Barış Akın

25年以上の経験を持つソフトウェアソリューションアーキテクト。FinTechと分散システム。OxiDB、Rill、Cobraの開発者。