話題のAlpine.jsをVue.jsと比較しながら見ていく

UI開発者 板垣

みなさんこんにちは、UI開発者の板垣です!

今月のGitHub Trending JavaScript部門で1位を獲得した、Alpine.jsをご存じでしょうか?
日本のフロントエンド界隈だとまだ耳にすることは少ないですが、じわじわとその名を浸透させていっています。

今回はそんなAlpine.jsをVue.jsと比較しつつ、簡単にご紹介いたします。

そもそもAlpine.jsとは

Vue.jsやReactの持つリアクティブかつ宣言的な性質を、簡単にマークアップに組み込むことができる軽量フレームワークです。
ディレクティブなどの基本構文はVue.jsを参考にしているため、Vue.jsを使ったことがある人であればすぐにでも始められます。

導入方法

Alpine.jsの導入方法は2通りあります。

CDNから読み込む

任意のHTMLファイルに下記script要素を読み込みます。

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.0.0/dist/alpine.js" defer></script>

※IE11のサポートが必要な場合は下記のIE11サポート版を読み込みましょう。

<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.0.0/dist/alpine-ie11.js" defer></script>

NPMからインストールする

プロジェクトディレクトリで下記コマンドを実行。

npm i alpinejs

その後、任意のファイルにimport 'alpinejs'と記述します。

構文について

現在、Alpine.jsには13のディレクティブと、5つのマジックプロパティと呼ばれるものが存在します。

  • Directive
    • x-data
    • x-init
    • x-show
    • x-bind
    • x-on
    • x-model
    • x-text
    • x-html
    • x-ref
    • x-if
    • x-for
    • x-transition
    • x-cloak
  • Magic Properties
    • $el
    • $refs
    • $event
    • $dispatch
    • $nextTick

Alpine.jsのディレクティブ = Vue.jsのディレクティブという認識で問題ありません。
マジックプロパティについては公式READMEで特に言及されていませんが、Vue.jsの接頭辞に$が付いたプロパティやメソッドにあたるものを、独自にマジックプロパティと定義しているようです。
つまり、マジックプロパティ = 便利な機能群くらいの認識で大丈夫でしょう。

これらの機能は接頭辞が異なるだけでVue.jsとほぼ同じ機能を提供しますが、いくつかAlpine.js独自の部分があるのでポイントを絞ってご紹介します。

x-data

Vue.jsのdataにあたるのがx-dataです。
要素のx-dataディレクティブにオブジェクト形式でデータを書き込むと、その要素およびその中にある要素内で記述したデータが扱えるようになります。

<div x-data="{show: false}">
    <p x-show="show">このテキストは表示されません</p>
</div>

機能ごとにデータを分割したい場合は、オブジェクト形式のデータを関数の返り値に指定します。

<div x-data="dropdown()">
    <button @click="open()">オープン</button>

    <div x-show="isOpen" @click.away="clone()">
        <p>テキスト</p>
    </div>
</div>

<script>
const dropdown = () => {
    return {
        isOpen: false,
        open () {
            this.isOpen = true;
        },
        clone () {
            this.isOpen = false;
        }
    }
};
</script>

x-init

x-initにはコンポーネントが初期化されるときに実行したい処理を書きます。これはVue.jsでいうとcreatedに近いです。
x-initの返り値として指定した関数は、Vue.jsのmountedのようにDOMの初期更新を行った後に実行されます。

<div x-data x-init="init()"></div>
<script>

const mounted = () => {
    alert('mounted');
};
const init = () => {
    alert('init');

    return mounted;
};
// 
</script>

このx-initx-dataディレクティブが存在しない場合は実行されないため注意が必要です。

x-on

イベントリスナーを登録できるディレクティブです。
このディレクティブ自体は、Vue.jsのv-onとさほど代わりはなく、@onという形で省略して書くことが可能です(今回紹介しないx-bindも同じく:という形で省略できます)。

Vue.jsと異なる点はイベント修飾子です。x-onには、以下の6つのイベント修飾子が存在します。

  • keydown
  • away
  • prevent
  • stop
  • window
  • once

awaywindowといった見慣れない修飾子があると思います。
away修飾子を付けると、イベントが対象の要素もしくはその子要素以外で発生した場合にのみ指定の処理が実行されるようになります。(モーダルウィンドウを作るときなどに役立ちそうですね。)

また、window修飾子を付けると、対象要素ではなくwindowオブジェクトにイベントリスナーが登録されます。
これは、ウィンドウの幅が変更されたり、スクロールが発生したときにコンポーネントの状態を変更したい場合に有用です。

x-text

x-textは要素のinnerTextをリアクティブに更新するためのディレクティブです。
AlpineにはVue.jsのようなテンプレート構文({{}})が存在しないため、データをテキストとして反映するためにはこのx-textを使用します。

使い方はx-textディレクティブの値に表示したいデータを入れるだけです。

x-transition

要素にアニメーションを加えるためのディレクティブです。Vue.jsでアニメーションを付けるには<transition>コンポーネントを使用しましたが、Alpine.jsではディレクティブを使用することに注意してください。

このディレクティブを細かく分けると以下の6つなります。

  • x-transition:enter
  • x-transition:enter-start
  • x-transition:enter-end
  • x-transition:leave
  • x-transition:leave-start
  • x-transition:leave-end

これらのディレクティブにCSSのクラスを指定することで、対象要素に対して各ディレクティブのタイミングで指定したクラスを追加・削除するようになります。

各ディレクティブの適用される流れについて軽く触れると、Enter系は要素の挿入前にx-transition:enterx-transition:enter-startが付与され、要素が挿入された1フレーム後にx-transition:enter-startが削除されます。それと同時にx-transition:enter-endが追加され、アニメーションが終了して1フレーム後にx-transition:enterx-transition:enter-endが削除されるようになっています。
Leave系は上の文章の「要素の挿入されるタイミング」という部分が、「要素が消されるタイミング」に変わるだけです。

下記の例では、要素の表示非表示をx-transitionを使用して切り替えています。

<head>
<style>
    .hide { opacity: 0; }
    .show { opacity: 1; }
    .transition { transition: 0.3s; }
</style>
</head>
<body>
    <div x-data="{ open: false }">
        <button x-on:click="open= ! open">表示・非表示の切り替え</button>

        <div x-show="open"
            x-transition:enter-start="hide"
            x-transition:enter="transition"
            x-transition:enter-end="show"
            x-transition:leave-start="show"
            x-transition:leave="transition"
            x-transition:leave-end="hide">
            <div>
                x-transitionのサンプル
            </div>
        </div>
    </div>
</body>

$dispatch

ここまでディレクティブを紹介してきましたが、こちらの$dispatchはマジックプロパティです。
$dispatchはカスタムイベントの作成とそのイベントをディスパッチできる機能で、内部的にはWeb標準のカスタムイベントを使用しているようです。
単純にカスタムイベントを簡単に発効するものなので、Vue.jsと比較するとどうこうというものではありませんが、強いていうならば$emitに近いかもしれません。

次の例では、ボタンをクリック時に$dispatchが実行され、イベント先でデータを更新しています。

<div x-data="{ msg: '' }" @hello="msg = $event.detail.msg">
    <p x-text="msg"></p>
    <button @click="$dispatch('hello', { msg: 'Hello' })">送信</button>
</div>

$dispatchの第一引数には「カスタムイベント名」、第二引数には「送信したいデータ」を指定します。第二引数で指定したデータは、イベント先の「$event.detail」から参照できます。

このプロパティを活用することで、コンポーネント間でデータの受け渡しが容易にできそうです。

以上が、Alpine.jsの特徴的な構文です。
ちなみに各ディレクティブの接頭辞x-は、v1リリース前までプロダクトネームが「Project-X」だったのでその名残でしょう。

実際に触ってみる

それでは最後に、Alpine.jsを使って作った簡単なディスクロージャとモーダルウィンドウをご覧ください。

コードのポイントとしては機能ごとにデータを分割している所です。分割したデータはエンドポイント要素のx-dataで、スプレッド演算子を使用して展開しています。

<div @modal-open="openModal()" x-data="{...disclosure(), ...modal()}">
    ...
</div>
<script>
const disclosure = () => {
    ...
};
const modal = () => {
    ...
};
</script>

おわりに

Vue.jsを触っていたからか、Alpine.jsの学習コストの低さにとても驚きました。
初回リリースからまだ3カ月ほどしかたっていない新しいフレームワークですが、完成度は高く、大規模サイトへ部分対応や小規模な診断コンテンツなどを作成するときに活用できそうです。