9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptAdvent Calendar 2022

Day 4

Alpine.jsでそれっぽくコードとマークアップを分離する

Last updated at Posted at 2022-12-03

TL;DR

addEventListenerのapline:initで色々するとJSとマークアップを分離できそう。

Alpine.jsってなんぞ

私とAlpine.jsの出会いは Laravel Livewireでした。
Laravelフレームワーク上でSPAライクな実装ができるようになるフレームワークなのですが、低レベルなところで色々やっている部分の基底技術がこのAlpine.jsです。

特徴としては以下のような感じです。

  • 15の属性、6のプロパティ、 2のメソッドから構成(※正確には18の属性、9のプロパティ、3のメソッド)。すごくシンプル
  • 紐づけするためのオブジェクトもフィールドまたはgetterプロパティでシンプル

兎にも角にもシンプルさが目立つイメージです。けれどもデータバインド周りの機能は十分なので、jQueryの代替として利用することもできそうです。

そのサンプル、インラインじゃね?

Alpine.jsの簡単なサンプルを見ると、以下のようなものが多いです。

<div x-data="{ count: 0 }">
    <button x-on:click="count++">Increment</button>
 
    <span x-text="count"></span>
</div>

上記のサンプルはAlpine.js start-here; building a counterのコードですが……

Alpine.jsってインラインじゃないと使えない……?

サンプルコードではx-data属性で使用するオブジェクト定義をしていて、x-on属性にはcountフィールドのインクリメント、x-text属性でinnerHTMLに対するバインディングをしています。

x-text属性は分かる。表示するのに紐付けないと何にもできないから。

しかし。

x-dataのオブジェクト指定、x-on属性のロジックはなんとかできない? 分離したくない?

複雑なデータを扱おうとすると、x-data属性に対して1行でいろんなことを書かないといけないため、シッチャカメッチャカになる未来が見えます。

<!-- こんな未来が見える -->
<div x-data="{fieldA: '', fieldB: '', fieldC: '', fieldD: '', fieldE: '', doSomething() { console.log('なにかする');}, }" >

マークアップとAlpine.jsの世界を分ける

バージョンなど

ライブラリ バージョン
Alpine.js 3.10.5
Pure.css 3.0.0

Pure.cssはおまけです。サンプルのUIにグリッドを導入したかったので。

実装サンプル in Codepen

See the Pen Alpinejs code behind by FilunK (@filunK) on CodePen.

上記サンプルは以下のことをしています。

  • windowサイズを追跡、表示
  • チェックボックスのチェック状態をバインド、チェックボックスやらボタンやらでいじくれるように

詳説

HTML、JSのソースを説明していきます。

HTML

<div x-data="alpineCodebehind">
  <div class="pure-g">
    <div class="pure-u-1">
      <label>
        <input type="checkbox" x-model="checked"/>
        チェックボックス
      </label>
      <button class="mx" x-on:click="toggle">チェックをイジる</button>
      <span x-text="checked"></span>
    </div>
    <div class="pure-u-1">
      <label>
        WIDTH:&nbsp;<span x-text="width"></span>
      </label>
    </div>
    <div class="pure-u-1">
      <label>
        HEIGHT:&nbsp;<span x-text="height"></span>
      </label>
    </div>
  </div>
</div>

インラインでよく見るサンプルとの違いは以下のポイントです。

  • x-dataで指定するのはJSのオブジェクトではなく、データの名前
  • x-on系統の属性にはロジックではなくメソッド名を指定

JS

// ES モジュール方式でのロード
import Alpine from "https://cdn.skypack.dev/alpinejs@3.10.5";

// Alpinejs INITイベントで初期化
document.addEventListener('alpine:init', () => {
  
  // Alpinejsの外部からAlpinejsの世界にバインドする変数を定義
  const windowSize = Alpine.reactive({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  // Alpinejsの外部世界: window.resizeイベント
  window.addEventListener('resize', (e) => {
    windowSize.width = window.innerWidth;
    windowSize.height = window.innerHeight;
  });
  
  // 画面のデータバインディング
  Alpine.data('alpineCodebehind', () => ({
    
    // チェック?
    checked: false,

    // computed: 幅
    get width () {
      return windowSize.width;
    },

    // computed: 高さ
    get height () {
      return windowSize.height;
    },

    // 振る舞い: トグル
    toggle () {
      this.checked = ! this.checked;
    },
  }));
});

// Alpinejsの開始
Alpine.start();

インラインでよく見るサンプルとの違いは以下のポイントです。

  • CDN直読み込みではない。
  • Alpine.start()の前にalpine:initイベントに対するイベントリスナーを実装
    • イベントリスナー内でAlpine.data()メソッドにてデータ名とマークアップの後ろに控えるフィールド・メソッドを実装

Alpine.data()メソッドの第1引数が、メソッドの表すデータの名前になります。これがマークアップ側でのx-data属性に指定する値です。

要は明示的にAlpine.start()メソッドが実行する前に予めイベントリスナーを実装することが必要なわけです。
CDN直読み込みだと、その時点で初期化のイベントが終了してしまうので、x-data属性にデータ名を指定できなくなってしまうんですね。知らんけど。

コレはおそらく、CodepenにおけるHTMLやJSのロードに関係する問題と思われます。
私自身が試しているわけではありませんが、CDN直読み込みでもalpine:initでいい感じに実装しているコードもネット上では見受けられるので。

今回、サンプルではESモジュール方式でAlpine.jsをインポートしています。このあたりは色々と選択肢があると思います。viteなりwebpackなりでバンドルする、なんてこともありでしょう。

分離した際の思わぬメリット

このコードだとHTMLとJSを分離することに成功しているわけですが、別のメリットもあります。

Alpine.jsの外の世界のイベントをAlpine.jsの世界にバインドできる 点です。

サンプルのコードでは、以下のステップでAlpine.jsの外からAlpine.jsの中の世界に情報を持ち込んでいます。

  1. Alpine.reactive()メソッドでバインド用のデータ※以下『REACTIVE』を用意
  2. 他のイベントリスナーの中で REACTIVE を弄る
  3. Alpine.data()メソッドの世界の中で REACTIVE を使用する

該当のコードをサンプルから抜き出すと以下のコードになります。

// Alpinejsの外部からAlpinejsの世界にバインドする変数を定義
const windowSize = Alpine.reactive({
    width: window.innerWidth,
    height: window.innerHeight,
});

// Alpinejsの外部世界: window.resizeイベント
window.addEventListener('resize', (e) => {
    windowSize.width = window.innerWidth;
    windowSize.height = window.innerHeight;
});

// 画面のデータバインディング
Alpine.data('alpineCodebehind', () => ({
    // computed: 幅
    get width () {
      return windowSize.width;
    },

    // computed: 高さ
    get height () {
      return windowSize.height;
    },
}));

サンプルではwindowサイズの変更をリアルタイムに表示するため、window.resizeイベントで幅と高さを取得して、算出プロパティとして実装しています。

最後に

Javascriptの世界はいろんな技術が生まれては廃れてを繰り返していて、技術を追っかけるのが大変な一方で楽しい領域でもあります。しかも思いもしないところから

実はな、これ、JSのライブラリを利用してるんだよ

みたいなところがあって(特にWebアプリ・システムを構築しようとすると)、新鮮な刺激になります。

Alpine.jsLaravel Livewireきっかけで知ったものですが、コレ単体でも色々とできそうな可能性を感じました。

IEが一応亡き者となった今、こういった新しい技術を取り込んでゆくにはよい機会でしょう。

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?