
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、ホントにIT業界はいろいろと変化が多いので、少しの時間が経つだけで「この技術」が「あの技術」になっていたりします。
そして、そんな感じで多く技術が生まれているJavaScript
界はLaravel
のように1強という状態ではなく、
- やっぱ
React
でしょ - それでも
Vue
が好き Svelte
だって負けてないぞ
とように戦国時代のような群雄割拠した状態が長く続いています…(どれが徳川家康になるのか注目してます)
そんな中、とある開発で使ったのがAlpine.js
でした。
正直あまり使っていなかったのですが、使ってみて素直にこう思いました。
もう、これでいんじゃない(所ジョージさん風)
ただし、お仕事としてはAlpine.js
のシェアは多いとは言えないので、やはりReact
、Vue
がベターだと思います(開発者が多いのでリクルートしやすいはず)が、個人開発やそれほど複雑な構成ではないサイトは、十分すぎるぐらい高機能だと感じました。
(それでも学習コストが低いのですぐ覚えられるとは思います)
しかし、ひとつ問題がありました。
それは・・・・・・
再利用可能なコンポーネントを作りにくい
です。
というのも、例えばVue
だとコンポーネントが簡単につくる仕組みがありますが、Alpine.js
ではちょっとした工夫が必要でした。
そこで
今回はAlpine.js
を使ってVue
のようなリアクティブなコンポーネントを作ってみたいと思います。
ぜひ何かの参考になりましたら嬉しいです。
「Alpine って『アルプスの』という
意味らしいです。
空気薄いから読まなくていいってこと」
開発環境: Laravel 10.x、Alpine.js 3
やりたいこと
今回は以下のようなコンポーネントをJavaScript側で再利用可能にします。
もちろんLaravel
のBlade
でもループさせれば表示することはできるのですが、Ajax
化したいとき(リアクティブ性を高めたいとき)に毎回ページを更新しないといけなくなるので、それを解決したいのが今回のメインどころです。
なお、今回は以下の条件が全て可能になるようにします
- (Ajaxを想定して)データが変更されたら表示が自動で変更になる
- パラメータは <script></script>内で指定できる
- 同じくパラメータは、直接(オブジェクトで) { … } としても指定できる
- あるデータによって表示/非表示を自動切り替えする
では楽しんでやっていきましょう
Laravelでコンポーネントをつくる
まずは、Laravel
でコンポーネントを作って、そこにAlpine.js
側で再利用できるコンポーネントを作っていきます。
今回、コンポーネント名は「ItemCard」にします。
では、以下のコマンドを実行してください。
php artisan make:component ItemCard
すると、以下2ファイルが作成されます。(ビューまで作成してくれて嬉しい限りです)
- app/View/Components/ItemCard.php
- resources/views/components/item-card.blade.php
では、それぞれ中身を変更していきましょう。
Laravel側
app/View/Components/ItemCard.php
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ItemCard extends Component
{
/**
* Create a new component instance.
*/
public function __construct(
public string $xData = '{}',
){}
/**
* Get the view / contents that represent the component.
*/
public function render(): View|Closure|string
{
return view('components.item-card');
}
}
変更したのは太字部分だけですが、これはAlpine.js
側で以下のようにx-data
パラメータが使えるようにするためです。( キャメルケースになっていることに注意してください)
<x-item-card x-data="data" />
※ なお、もしこのコンストラクタの書き方を見たことがない方はPHP 8
から使えるようになった省略バージョンですので、ぜひ省コード化に役立ててください(この場合、メンバ変数の定義と$this->xData = $xData;
は書かなくてよくなりました)
これでLaravel
側は完了です。
Alpine.js側
続いてAlpine.js
側です。
resources/views/components/item-card.blade.php
<div x-data="{ ...{{ $xData }}, ...itemCardMixin }">
<div class="max-w-sm p-6 bg-white border border-gray-200 rounded-lg shadow mb-5">
<h5 class="mb-2 text-2xl font-bold tracking-tight text-gray-900" x-text="item.title"></h5>
<p class="mb-3 font-normal text-gray-700" x-text="item.content"></p>
<template x-if="hasUrl()">
<a :href="item.url" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-blue-700 rounded-lg">
続きはこちら
</a>
</template>
</div>
</div>
この中で重要なのが、x-data
の部分です。{{ $xData }}
の部分は、先ほどのパラメータが入ってくるため、例えばレンダリングすると以下のようになります。
<div x-data="{ ...itemData, ...itemCardMixin }">
そして、このitemData
は以下のように<script> 〜 </script>
内に定義することになります。
<script>
const itemData = {
item: {
id: 999,
title: 'タイトル',
content: 'コンテンツ',
url: 'https://specific.example.com',
}
};
</script>
ただしパラメータの指定はこれだけじゃなく、以下のようなループにも対応しています。
<div class="mb-5" x-data="data">
<template x-for="item in items" :key="item.id">
<x-item-card x-data="{ item }" />
</template>
<x-item-card x-data="directData" />
</div>
この(オブジェクトとして)直接データを指定する場合のレンダリングは以下のようになります。
<div x-data="{ ...{ item }, ...itemCardMixin }">
では、itemCardMixin
はなんなのかというと、「コンポーネントで使う関数や変数」が入ってきます。
今回の場合で言うと、URLがデータ内に入っているかどうかをチェックするhasUrl()
という関数がありますが、これがコンポーネントで使う関数となります。
実を言うと、当初はコンポーネント内に以下のように埋め込むように考えていました。
<div x-data="{
hasUrl() {
return this.item.url;
}
}">
しかし、もしLaravel
側のループで表示されるとすると、全ての場所にこのhasUrl(){ ... }
が表示されることになり正直「コードが美しくない…」と思い変更しました。
そこで、itemCardMixin
も以下のように<script> 〜 </script>
タグ内で書くようにしました。
<script>
// 省略
const itemCardMixin = {
hasUrl() {
return this.item.url;
}
};
</script>
こうすることで、このコードは1度だけ表示されるようになります。
※ ただし、毎回呼び出す必要があるので、例えばcomponents.js
というようなファイルに外部化しておかないといけないのがデメリットですが、これが一番ベターな方法という結論になりました…何かいいほうほうはあるのでしょうか
完成したコード
では、今回やりたいことのすべての条件を満たした全コードになります。
<html>
<head>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.1/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com/3.3.2"></script>
</head>
<body>
<div class="p-5">
<div class="mb-5" x-data="data">
<h1 class="mb-3">Card</h1>
<template x-for="item in items" :key="item.id">
<x-item-card x-data="{ item }" />
</template>
<x-item-card x-data="directData" />
</div>
</div>
<script>
const data = {
items: [
{ id: 1, title: '1番目のタイトル', content: '1番目のコンテンツ', url: 'https://example.com' },
{ id: 2, title: '2番目のタイトル', content: '2番目のコンテンツ' },
{ id: 3, title: '3番目のタイトル', content: '3番目のコンテンツ'},
],
init() {
// リアクティブの確認のため、3秒後にデータを更新しています
setTimeout(() => {
this.items = [
{ id: 4, title: '4番目のタイトル', content: '4番目のコンテンツ', url: 'https://four.example.com' },
];
}, 3000)
}
};
const directData = {
item: {
id: 999,
title: 'タイトル',
content: 'コンテンツ',
url: 'https://specific.example.com',
}
};
const itemCardMixin = {
hasUrl() {
return this.item.url;
}
};
</script>
</body>
</html>
テストしてみる
では、実際にテストしてみましょう
今回はせっかくなのでデモページを用意しました。
シンプルな内容ですが、ぜひ試してみてください。m(_ _)m
企業様へのご提案
今回ご紹介したAlpine.js
はまだまだ知名度やシェアは低いですが、Laravel
の標準として採用されていますし、何より学習コストが低い(≒すぐ使えるようになりやすい)ので、もしチーム全体がフロントエンドに習熟していない場合は選択肢のひとつになると考えています。
また、これも「シンプルだけど強力な開発テクノロジー」のLivewire
はバージョン3からAlpine.js
とより親密になると言われているため、今後シェアを集める可能性も秘めています。
もしAlpine.js
を使った開発をご希望の方はぜひお問い合わせからご相談ください。
お待ちしております。
おわりに
ということで、今回は「Laravel + Alpine.js」で再利用可能なコンポーネントを作ってみました。
最初は、alpinejs-component というパッケージを使うことを検討していましたが、試してみたところLaravel
との連携がうまくいかないようで断念し、今回の内容にたどり着きました。
それにしても、JavaScript
界は今後どうなるのでしょうね。
正直Vite
になった今でもビルドは好きになれなかったりするので、Alpine.js
やVue 2
のようなシンプルさがいいのですが、それだとリプレイス業務が減ってエンジニアの仕事が減ったりするんでしょうか…
ちなみに、Vue
はバージョン2
は2023.12.31
にサポートが切れるようですし、3
はもう別のフレームワークになってしまいました。
誰かVue lite
みたいなシンプルバージョンを作ってくれないでしょうか。(それがAlpine.js
)
今夜も夜しか眠れなさそうです(笑)
ではでは〜
「けっこうよく寝る方です
」