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

sharkHead 程式技術 3個月前 • 0

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 預設會有一個 div tag
這裡有一點需要注意,所有的 Livewire components 都必須在同一個父元素中
因此建議不要刪除預設的 div tag,接下來回覆區塊會用到的 Livewire components,都會包在這個 div tag 中

<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 class="form-control" placeholder="content"
                wire:model="content" id="floatingTextarea"
                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 tag 使用 Livewire 的  wire:model 來對其內容與後端進行 Data Binding(資料綁定)
有使用過 Vue.js 的朋友對 Data Binding 應該不陌生,Livewire 的 Data Binding 也是類似的概念
可以讓前端頁面上的資料與後端的的資料進行同步

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

<?php

...

class Replies extends Component
{
    
    // 前端的 textarea 有設定 wire:model="content"
    // 代表 textarea 的內容會與 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);
    }
    
    ...
}

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

%E5%9C%96%E7%89%87.png
太神辣!Livewire

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

%E5%9C%96%E7%89%87%281%29.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 --}}
                <textarea class="form-control" placeholder="content"
                {{-- 使用 .debounce.500ms 減少輸入時發送的請求數目 --}}
                wire:model.debounce.500ms="content" id="floatingTextarea"
                style="height: 100px;"></textarea>

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

 

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

參考資料
Laravel Livewire - Validation


sharkHead

PHP 與 Python 菜雞工程師
最近在努力學習 TypeScript,希望可以突破慧根的限制