エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

HTML要素に適用するコードを再利用する、Alpine.jsで。

要素に適用するコードを再利用する、Alpine.jsで。

初期のリリースでは、HTML の中にデータやコードを埋め込むスタイルだった Alpine.js ですが、v3.0 以降はその分離と再利用のためのしくみが整備されてきているようです。

今月になって新しいしくみが提供されました。

github.com

alpinejs.dev

# Alpine.bind

Alpine.bind(...) provides a way to re-use x-bind objects within your application.

HTML と JavaScript は分離したい勢としては、うれしい仕様です。

ここでは、押すと表示と状態が変化するボタンを例に説明します。

f:id:E_Mattsan:20220130105340p:plain

未分離のAlpine.js

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body>
    <div x-data="{label: 'Greet', disabled: false}">
      <button x-text="label" @click="label = 'Hello!'; disabled = true" :disabled="disabled"></button>
    </div>
  </body>
</html>

未分離の書き方では、x-dada 属性にデータを記述し、x-on:click(省略形は @click )にクリック時に実行するコードを記述します。

x-bind:disabled(省略形は :disabled )は、HTML の属性 disabledJavaScript の値 disabled を結びつけていて、JavaScriptの値の変化が HTML の属性に反映されるようになっています。

これでは少し規模が大きくなるだけで、コードが混乱することが簡単に想像できます。

Alpine.js を最初に見つけたとき、関心をよせたものの積極的に利用しようとは考えなかったのも、このあたりがその理由でした。

HTMLとデータを分離したAlpine.js

HTML の要素とデータやコードを結びつけるための Alpine.data という関数が提供されています。

alpinejs.dev

Alpine.data の使い方については、以前にも記事を書いていますので、そちらも参照してみてください。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body>
    <div x-data="greeting">
      <button x-text="label" @click="greet" :disabled="disabled"></button>
    </div>

    <script>
      document.addEventListener('alpine:init', () => {
        Alpine.data('greeting', () => {
          return {
            label: 'Greet',
            disabled: false,

            greet() {
              this.label = 'Hello!'
              this.disabled = true
            }
          }
        })
      })
    </script>
  </body>
</html>

これで大きなところは分離できましたが、まだ x-text, @click, :disabled といった属性が HTML に埋め込まれたままになっています。

x-bind は HTML の属性と JavaScript の値を結びつけるしくみですが、独自の名前を割り当てることで属性をまとめて JavaScript 内に記述することができるようになります。

ただし。 見てのとおり、「ごちゃついた感じ」はぬぐえません。 個人的な感想は「なんと面妖なコード」です。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body>
    <div x-data="greeting">
      <button x-bind="greetingButton"></button>
    </div>

    <script>
      document.addEventListener('alpine:init', () => {
        Alpine.data('greeting', () => {
          return {
            label: 'Greet',
            disabled: false,

            greetingButton: {
              ['x-text']() {
                return this.label
              },

              ['@click']() {
                this.label = 'Hello!'
                this.disabled = true
              },

              [':disabled']() {
                return this.disabled
              }
            }
          }
        })
      })
    </script>
  </body>
</html>

データとふるまいを分離した Alpine.js

新たに導入された Alpine.bind は、greetingButton の部分をデータの定義から分離します。

ただし分離とは言ってもデータを定義する部分からの分離で、データを参照したり更新したりといった部分でつながっています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@exampledev/new.css@1.1.2/new.min.css">
  </head>
  <body>
    <div x-data="greeting">
      <button x-bind="greetingButton"></button>
    </div>

    <script>
      document.addEventListener('alpine:init', () => {
        Alpine.data('greeting', () => {
          return {
            label: 'Greet',
            disabled: false
          }
        })

        Alpine.bind('greetingButton', () => {
          return {
            type: 'button',

            'x-text'() {
              return this.label
            },

            '@click'() {
              this.label = 'Hello!'
              this.disabled = true
            },

            ':disabled'() {
              return this.disabled
            }
          }
        })
      })
    </script>
  </body>
</html>

データが分離されているということで、単独のコンポーネントとしての再利用には向いていませんが、逆に分離されているために別々のデータに割り当てることができるというメリットがあります。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
  </head>
  <body>
    <div x-data="greeting1">
      <button x-bind="greetingButton"></button>
    </div>

    <div x-data="greeting2">
      <button x-bind="greetingButton"></button>
    </div>

    <script>
      document.addEventListener('alpine:init', () => {
        Alpine.data('greeting1', () => {
          return {
            label: 'Greet',
            message: 'Hello!',
            disabled: false
          }
        })

        Alpine.data('greeting2', () => {
          return {
            label: 'あいさつしましょう',
            message: 'やはぁ',
            disabled: false
          }
        })

        Alpine.bind('greetingButton', () => {
          return {
            type: 'button',

            'x-text'() {
              return this.label
            },

            '@click'() {
              this.label = this.message
              this.disabled = true
            },

            ':disabled'() {
              return this.disabled
            }
          }
        })
      })
    </script>
  </body>
</html>

Alpine.js の活きる道

Alpine.js は フロントエンド向けの JavaScript フレームワークですが、より普及しているフレームワークと違って、HTML を構築する部分がありません。 このため、HTML 部分は Alpine.js のコードとは別に自分で組み立てる必要があります。

これはフレームワークとして「弱い」部分ではあると思いますが、一方でその「主張しない」ことが Alpine.js の強みになっていると考えています。

Ruby on RailsPhoenix Framework のように自前で HTML を組み立てる機構を持つサーバサイドのフレームワークに、他に特別な設定を必要せずに JavaScript パッケージの一つとして組み込むことができ、ほとんど衝突することなく利用することができるのは、サーバサイドの開発をする者にとって大きな利点かなと思います。

いつか読むはずっと読まない:あなたに見えているものと、わたしに見えているものは、違うということ

仕事をリモートでするようになってそこそこ時間が経ちましたが。 コミュニケーションをとりやすくなったのか、とりにくくなったのか、だいぶ個人差があるような印象。