🌻

プチ Livewire 的な事を Laravel の標準機能でやってみる

2023/10/23に公開

前書き

ユーザーが最初にアクセスした際は、サーバ側で描画したデータを返し、それ以降は、AJAX で取得して必要な部分だけを書き換えたいことはたまにあります。
Livewire とか使ったりすれば、その辺サクッとできたりする所ではありますが 、本当にちょっとしたものの場合、ライブラリーを入れるのも少し気が引けるところがあります。
そこで今回は Laravel の標準機能だけでプチ Livewire 的なことをやってみたいと思います 。

本題

ということで、この記事では、カレンダーの表示を想定したいと思います。最初はサーバ側でカレンダーを含む全体の HTML を返すけど、それ以降の年月の移動については、AJAX を使ってカレンダー部分のみをピンポイントで書き換えます。

とは言え、カレンダー自体の実装は本題とは関係無いので、そこは思いっきり省略します。代わりに年月移動の機能だけを見て行きます。

大体想像が付くと思いますが、Laravel のコンポーネント機能を使います。今回はクラスベースのコンポーネントを作成して行きます。ということで、以下のコマンドを叩きます。

php artisan make:component Calendar

これで、
resources/views/components/calendar.blade.php
app/View/Calendar.php
の 2 ファイルが生成されました。

Calendar.php は、以下の感じにします。

Calendar.php
<?php

namespace App\View\Components;

use Carbon\CarbonImmutable;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;

class Calendar extends Component
{
    public function __construct(private string $ym = '')
    {
    }

    public function render(): View|Closure|string
    {
        $ym = $this->ym;

        if (! $this->isDate($ym.'-01')) { // 日付のチェック isDate() の実装は略
            $ym = today()->format('Y-m');
        }

        $thisMonth = new CarbonImmutable($ym.'-01');
        $nextMonth = $thisMonth->modify('+1 month');
        $prevMonth = $thisMonth->modify('-1 month');

        return view('components.calendar',
            compact('thisMonth', 'nextMonth', 'prevMonth'));
    }

$ym は、ユーザーから受け取った年月を格納するプロパティで、2012-08 などの形式を想定しています。また、前月、来月用のリンクの為の nextMonth などを view 側に渡しています。

その view の calendar.blade.php は、下記の感じ。

calendar.blade.php
<div id="js-calendar">
    <h2>{{ $thisMonth->format('Y年m月') }}</h2>
    <ul>
        <li><a href="{{ route('calendar-ajax', $prevMonth->format('Y-m')) }}"
            onclick="return moveMonth(this)">前月</a></li>
        <li><a href="{{ route('calendar-ajax', $nextMonth->format('Y-m')) }}"
            onclick="return moveMonth(this)">次月</a></li>
    </ul>

    <!-- 実際はカレンダーの表示がこの辺に来る -->

</div>

前月、次月をクリックすると、moveMonth() 関数を呼び出してカレンダーの年月を移動します。(中身は後述)

web.php は、以下の感じです。

web.php
use App\View\Components\Calendar;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return view('welcome');
});

Route::get('calendar-ajax/{ym}', function ($ym) {
    return Blade::renderComponent(new Calendar($ym));
})->where('ym', '\d{4}-\d{2}')->name('calendar-ajax');

URL は、TOP画面(/)を想定しています。最初のルーティングが最初のアクセス用。2つ目のルーティングが、2回目以降のアクセス用(AJAX 用)です。

2つ目の

return Blade::renderComponent(new Calendar($ym));

の部分が微妙にポイントなのですが、これで Calendar コンポーネントのみを呼び出して結果を返却しています。Blade::renderComponent(new コンポーネント名($ym)) という感じですね。第一引数で、 コンポーネントに年月情報 $ym も引き渡しています。

この便利機能のお蔭で、コンポーネントだけを呼び出す view を作成して、その view を指定して return して、、、、とかしなくて済みますね。

welcome.blade.php は、以下の感じです。

welcome.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>カレンダー</title>
</head>
<body>

<x-calendar />    ← カレンダーコンポーネントの呼び出し

<script>
function moveMonth(ele) {
    fetch(ele.href)
    .then((response) => response.text())
    .then((text) => {
        document.getElementById("js-calendar").innerHTML = text;
    });

    return false;
}
</script>

</body>
</html>

最初のアクセスでは、<x-calendar /> が呼び出されてサーバ側で描画されますが、それ以降は、AJAX(ここでは、fetch を使用)でコンポーネントから返されたデータを部分的に置き換えています。(流石に JS の力も少し借ります…)

大体以上になります。
より贅沢を言えば、history.pushState() とか使ったりして、URL も書き換えたりしたらベターかも知れませんが、その辺は今回は省略です。

余談その1

今回は、web.php に直接処理を書いてしまいました。まぁ、実質1行だからいいのですが、もし「それは微妙。でもコントローラを作るのも嫌」という贅沢な悩みがある場合は、Calendar コンポーネントの方にその処理を書くのは如何でしょうか。

web.php
use App\View\Components\Calendar;

Route::get('', [Calendar::class, 'index']);
Route::get('calendar-ajax/{ym}', [Calendar::class, 'ajax'])
    ->where('ym', '\d{4}-\d{2}')->name('calendar-ajax');
Calendar
class Calendar extends Component
{
    public function index()
    {
        return view('welcome');
    }

    public function ajax($ym)
    {
        return Blade::renderComponent(new Calendar($ym));
    }

余談その2

Laravel 10.17 から、クラスベースのコンポーネント作成時にテスト用のファイルも同時に生成できるようになっています。

例:

php artisan make:component Foo --test

無名コンポーネントでは生成されません。

これは何気に筆者によるプルリクですが、こういう地味というか、他人様のプルリクに乗るだけのプルリクは、ちょっと得意になってきたかも知れません 😄(笑
https://github.com/laravel/framework/pull/47894

雑感

今回見た Blade::renderComponent については、下記のプルリクが参考になります。
https://github.com/laravel/framework/pull/40745

こういうやり方もあるぞ!とかありましたら、コメント下さい。

Discussion