不寫 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);
}
// ...
}
這時候我們就可以在表單中輸入一個字看看,你就會發現雖然沒有送出表單,但是下方依然會出現錯誤訊息。
打開瀏覽器的開發者工具,並點選網路來監測網路請求,然後試著在打些字看看,你會發現前端會不停的發送 XHR 請求至後端,如同 AJAX 一般,Livewire 也是使用類似的方式,讓前後端進行資料的交換。
為了避免遭受 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>
{{-- ... --}}
上篇先到這邊告一個段落,敬請期待下回~