不寫 JavaScript,就讓網站變成 SPA!Laravel Livewire 初體驗(上)

Livewire 其實已經推出一段時間,但直到 Laravel 8 的 Jetstream 推出,小弟我才知道 Livewire。

Jetstream 在前端上有兩種選擇:

  • Vue.js 搭配 Inertia
  • Blade 搭配 Livewire

一般來說如果網頁要做成 SPA ,那麼就不會使用到 Laravel 的 Blade 樣板,這對喜歡 Blade 樣板的人來說,無疑是一個沉痛的打擊,而 Livewire 就是為了滿足 Blade 樣板控而存在的套件。

那麼 Livewire 的用途是什麼?

Livewire 官方網站是這麼介紹的。

Livewire is a full-stack framework for Laravel that makes building dynamic interfaces simple, without leaving the comfort of Laravel. 

看不懂得自己丟 Google 翻譯。

沒錯,只寫 Laravel,但是我們依然可以成為全端工程師,如此夢幻的套件,對我這種前端沒慧根的菜雞工程師,具有莫大的吸引力,根本沒有不拿來玩玩的理由。

此篇文章會以 Livewire 實作網路上常用的文章留言板,一般來說,留言回覆通常都是即時性的,填好回覆表單按下按鈕送出後,會直接更新回覆列表,並不需要重新整理頁面,用戶體驗會好非常多。

安裝 Livewire


首先先安裝 Livewire。

composer require livewire/livewire

然後在會用到 Livewire 的 blade 頁面加上需要的 JavaScript。

...
    @livewireStyles
</head>
<body>
    ...
    
    @livewireScripts
</body>
</html>

接下來我們使用 artisan make 指令產出回覆區塊的 Livewire component。

php artisan make:livewire replies

上述指令會產出兩個檔案。

  • app/Http/Livewire/Replies.php
  • resources/views/livewire/replies.blade.php

Replies.php 一開始會有一個 render 的方法,用來渲染 replies.blade.php 這個 view 的內容,可以把這個檔案想成是回覆區塊的 Controller,後端業務邏輯處理的部分像是表單驗證、讀取與儲存資料…等,都會在 Replies.php 中完成。

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class Replies extends Component
{
    public function render()
    {
        return view('livewire.replies');
    }
}

replies.blade.php 預設會有一個根元素 (root element) <div>

這裡有一點需要注意,所有的 Livewire components 都必須有一個唯一的根元素。如果你的 component 有兩個根元素,那麼第二個根元素不會有任何效果。

<div>
</div>

<!-- Livewire 對這個元素的所有操作都會失效 -->
<div>
</div>

因此建議不要刪除預設的 <div>,接下來回覆區塊會用到的 Livewire components,都會包在這個根元素中。

<div>
    <!-- 所有的 Livewire components 都會放在這裡 -->
</div>

然後在 blade 檔案中引入 replies.blade.php

<!-- 回覆區塊 -->
@livewire('replies')

回覆表單驗證


一般來說表單的內容都需要經過驗證,例如防止空白留言或是字數太多,用戶填妥表單送出後,資料傳遞至後端 Controller 進行驗證並返回驗證結果,這樣的流程都會讓網頁有「換頁」的動作,用戶體驗上會打點折扣,使用 Livewire,不需要送出表單,就可以即時的對表單內容進行驗證。

先在 replies.blade.php 中新增一個回覆表單,下方預留一個錯誤訊息的顯示區塊。

<div>
    <!-- 評論回覆 -->
    <div class="card shadow mb-4">
        <div class="card-body p-4">

            <div class="form-floating mb-3">
                <!-- 這裡使用 wire:model 進行 Data Binding -->
                <textarea wire:model="content" id="floatingTextarea"
                placeholder="content" class="form-control"
                style="height: 100px;"></textarea>

                <label for="floatingTextarea">留個話吧~</label>
            </div>

            <div class="d-flex justify-content-between">
                <!-- 錯誤訊息顯示 -->
                <div class="d-flex justify-content-center align-items-center">
                    @error('content') <span class="text-danger">{{ $message }}</span> @enderror
                </div>
            </div>
        </div>
    </div>
</div>

上面的範例有使用前端框架 Bootstrap 5,觀看此篇文章的朋友,沒有使用也沒關係,範例依然能夠正常運作,只是頁面會比較醜而已

<textarea>  的屬性中,我們使用 Livewire 的  wire:model 來對其 value 與後端的變數進行 Data Binding(資料綁定)。

有使用過 Vue.js 的朋友對 Data Binding 應該不陌生,Livewire 的 Data Binding 也是類似的概念
可以讓前端頁面上的資料與後端的的資料進行同步。

再來更新 Replies.php 的內容如下。

<?php

...

class Replies extends Component
{
    
    // 前端的 textarea 有設定 wire:model="content"
    // 代表 textarea 的 value 會與 Replies 類別的屬性 $content 值是同步的
    public $content;

    // 表單內容的驗證規則
    protected $rules = [
        'content' => ['required', 'min:2', 'max:400'],
    ];

    // 驗證失敗的錯誤訊息
    protected $messages = [
        'content.required' => '請填寫回覆內容',
        'content.min' => '回覆內容至少 2 個字元',
        'content.max' => '回覆內容至多 400 個字元',
    ];

    // 即時判斷表單內容是否符合 $rules
    public function updated($propertyName)
    {
        $this->validateOnly($propertyName);
    }
    
    ...
}

這時候我們就可以在表單中輸入一個字看看,你就會發現雖然沒有送出表單,但是下方依然會出現錯誤訊息。

2021_07_14_19_33_17_60eecb7dbd3cd.png
太神辣!Livewire

打開瀏覽器的開發者工具,並點選網路來監測網路請求,然後試著在打些字看看,你會發現前端會不停的發送 XHR 請求至後端,如同 AJAX 一般,Livewire 也是使用類似的方式,讓前後端進行資料的交換。

2021_07_14_19_33_27_60eecb87d01fe.png
資料同步就是這樣辦到的

為了避免遭受 CSRF 的攻擊,Livewire 在每次請求中塞入一串 hash 值 checksum,查看每次請求的內容,都可以看到帶有 hash 值的 checksum,並且每次請求都會是不一樣的值。

"serverMemo":{
    ...
    "checksum": "df19023f7e825582eaaec6bc8e5582650e3b166e110dc0a4487d698e869d3085"
}

如果擔心因為請求過多加重伺服器負擔,可以在 wire:model 後面加上 .debounce.500ms,讓預設的 150ms 改成 500ms,減少輸入表單時對伺服器發送的請求。

...
            <div class="form-floating mb-3">
                <!-- 這裡使用 wire:model 進行 Data Binding -->
                <!-- 屬性中有使用 .debounce.500ms 減少輸入時發送的請求數目 -->
                <textarea
                    class="form-control"
                    placeholder="content"
                    wire:model.debounce.500ms="content"
                    id="floatingTextarea"
                    style="height: 100px;"
                ></textarea>

                <label for="floatingTextarea">留個話吧~</label>
            </div>
...

上篇先到這邊告一個段落,敬請期待下回~

參考資料


Laravel Livewire - Validation

sharkHead
written by
sharkHead

後端工程師, PHP 基金會每月 5 鎂小額贊助人 稍微擅長 PHP、Python 與 Google Search,偶爾寫寫 TypeScript 對於逗號後面必須加空格有著絕對的堅持