文件 #4
open模組 3:Livewire 3 核心概念(8–10 小時)
0%
Description
- 安裝與 Livewire 3 介紹(全 PHP 反應式框架、與 Blade 完美整合)。
- 建立第一個 Livewire Component(Class + View)。
- 資料綁定(
wire:model、Deferred vs Live、.live 修飾詞)。 - 屬性(Properties)、Computed Properties、Actions(Methods)。
- 表單處理(Validation、Real-time Validation、Form Objects)。
- 事件系統(
dispatch()、wire:navigate、Component Communication)。 - Lazy Loading、Polling、Wire:Loading、Wire:Confirm。
- 檔案上傳(Temporary Uploads、Preview、Validation)。
- 實作:Live Search、Counter、Todo List、Dependent Dropdowns。
JH Updated by Jeffery Hsu about 5 hours ago
這個模組的內容非常豐富,Livewire 3 也是現代 Laravel 生態系中最具革命性的技術之一。為了確保你能完全吸收並實作成功,我會將模組 3 分為 三個部分 來逐步講解。
今天我們先進行 第一部分,專注於:Livewire 3 核心概念、建立元件、資料綁定(Deferred vs Live),並直接帶你完成 Counter(計數器) 與 Live Search(即時搜尋) 的實作。
第一步:Livewire 3 核心概念與環境確認¶
什麼是 Livewire?
Livewire 是一個能讓你用 純 PHP 來撰寫動態、反應式 (Reactive) 前端介面的框架。你不需要寫任何 JavaScript (如 Vue 或 React) 或手動處理 AJAX 請求。當使用者在畫面上點擊按鈕或輸入文字時,Livewire 會自動在背景發送輕量級的網路請求到伺服器,執行你的 PHP 邏輯,然後精準地只更新畫面上變動的部分。
環境確認:
因為我們在模組 1 使用了 Laravel Breeze (Livewire + Flux) Starter Kit,所以 Livewire 3 已經內建安裝好了,無需再執行 composer require livewire/livewire。
第二步:建立第一個元件與核心三元素 (屬性、動作、視圖)¶
我們直接透過實作 Counter (計數器) 來學習核心概念。
1. 建立元件
在終端機執行:
php artisan make:livewire Counter
這會自動產生兩個檔案:
-
Class (邏輯):
app/Livewire/Counter.php -
View (畫面):
resources/views/livewire/counter.blade.php
2. 撰寫 Class (定義屬性 Properties 與動作 Actions)
打開 app/Livewire/Counter.php:
namespace App\Livewire;
use Livewire\Component;
class Counter extends Component
{
// 1. 屬性 (Properties):這裡的 public 變數會自動暴露給前端視圖
public int $count = 0;
// 2. 動作 (Actions):前端觸發的方法
public function increment()
{
$this->count++;
}
public function decrement()
{
$this->count--;
}
public function render()
{
return view('livewire.counter');
}
}
3. 撰寫 View (視圖與事件綁定)
打開 resources/views/livewire/counter.blade.php。在 Livewire 3 中,根元素不強制只能有一個 div,但保持乾淨的結構是好習慣:
<div class="p-6 bg-white rounded-lg shadow-md text-center">
<h2 class="text-2xl font-bold mb-4">基礎計數器</h2>
<div class="flex justify-center items-center gap-4">
<flux:button wire:click="decrement" variant="danger">-</flux:button>
<span class="text-3xl font-mono">{{ $count }}</span>
<flux:button wire:click="increment" variant="primary">+</flux:button>
</div>
</div>
4. 在頁面中渲染元件
打開我們模組 2 建立的 resources/views/dashboard.blade.php,把元件放進去:
<x-layouts.app>
<x-slot:title>我的儀表板</x-slot>
<div class="max-w-7xl mx-auto p-6 space-y-6">
<livewire:counter />
</div>
</x-layouts.app>
驗證:現在去瀏覽器重新整理儀表板,點擊 + 和 - 按鈕,數字會即時變動,且完全不需要重新載入網頁!
第三步:資料綁定 (Data Binding) 與 Live Search 實作¶
Livewire 3 的一大變革是:預設的資料綁定是 Deferred (延遲的)。這代表當你輸入文字時,不會每打一個字就發送請求,而是在你執行某個 Action (例如點擊送出按鈕) 時,才會把資料一次送給後端。這極大地節省了伺服器效能。
但如果是 Live Search (即時搜尋),我們需要每打一個字就更新畫面,這時就要加上 .live 修飾詞。
1. 建立 Live Search 元件
php artisan make:livewire StockSearch
2. 撰寫 Class (包含 Computed Properties)
這裡我們運用 #[Computed] 屬性。它會快取運算結果,避免在同一個 Request 中重複查詢資料庫。為了展示,我們模擬一個搜尋台灣 AI 概念股與半導體供應鏈的資料庫:
打開 app/Livewire/StockSearch.php:
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\Computed;
class StockSearch extends Component
{
// 綁定到前端輸入框的字串
public string $search = '';
// 模擬資料庫
protected array $allStocks = [
['code' => '2317', 'name' => '鴻海', 'sector' => 'AI 伺服器'],
['code' => '2328', 'name' => '廣宇', 'sector' => '連接器與線束'],
['code' => '2330', 'name' => '台積電', 'sector' => '晶圓代工'],
['code' => '8028', 'name' => '昇陽半導體', 'sector' => '再生晶圓'],
['code' => '3231', 'name' => '緯創', 'sector' => 'AI 伺服器'],
];
// Livewire 3 新寫法:使用 #[Computed] 取代舊版的 getFooProperty()
#[Computed]
public function results()
{
if (empty($this->search)) {
return [];
}
return array_filter($this->allStocks, function ($stock) {
return str_contains($stock['code'], $this->search) ||
str_contains($stock['name'], $this->search);
});
}
public function render()
{
return view('livewire.stock-search');
}
}
3. 撰寫 View (使用 wire:model.live)
打開 resources/views/livewire/stock-search.blade.php:
<div class="p-6 bg-white rounded-lg shadow-md mt-6">
<h2 class="text-xl font-bold mb-4">即時個股搜尋 (Live Search)</h2>
<flux:input
wire:model.live.debounce.300ms="search"
placeholder="請輸入股票代號或名稱 (例如: 2328, 廣宇)..."
icon="magnifying-glass"
/>
<div class="mt-4">
@if(!empty($search))
<ul class="space-y-2">
@forelse($this->results as $stock)
<li class="p-3 border rounded hover:bg-gray-50 flex justify-between">
<span class="font-bold text-blue-600">{{ $stock['code'] }} {{ $stock['name'] }}</span>
<span class="text-sm text-gray-500">{{ $stock['sector'] }}</span>
</li>
@empty
<li class="p-3 text-gray-500">找不到符合「{{ $search }}」的股票。</li>
@endforelse
</ul>
@endif
</div>
</div>
4. 將搜尋元件加入儀表板
再次打開 resources/views/dashboard.blade.php,在剛剛的 Counter 下方加入:
<livewire:stock-search />
第四步:驗證你的作法¶
- 確保伺服器 (
php artisan serve) 正在運行。 - 回到瀏覽器,進入你的儀表板頁面。
- 驗證 Counter: 點擊加減號,確認畫面不閃爍即可更新數字。
-
驗證 Live Search: 在搜尋框中輸入
2,停頓一下,下方應該會立刻滑出 2317、2328、2330 的選項。接著輸入廣宇或2328,列表應該會精準過濾到只剩一筆。
如果以上都順利執行成功,就代表你已經完全掌握了 Livewire 3 的資料流、雙向綁定與即時反應機制。
準備好進入 模組 3 第二部分 (表單處理、驗證、Form Objects 與 Todo List 實作) 時,請隨時告訴我!
JH Updated by Jeffery Hsu about 5 hours ago
這進入 模組 3 的第二部分。這一次我們將專注於 表單處理 (Form Handling) 與 驗證 (Validation),並實作一個完整的 Todo List。
在 Livewire 3 中,最推薦的處理方式是使用 Form Objects。這可以將表單的邏輯、驗證規則與主元件分離,讓程式碼更整潔且易於維護。
第一步:理解 Form Objects¶
Form Objects 是 Livewire 3 的新特性。當你的表單欄位很多時,不需要在主元件中寫一堆 public $title, public $content 等變數,而是封裝在一個專門的類別中。
1. 建立 TodoForm 物件
雖然沒有直接的 make:form 指令,但我們可以手動建立一個繼承自 Livewire\Form 的類別。
在 app/Livewire/ 下建立一個 Forms 資料夾,並新增 TodoForm.php:
namespace App\Livewire\Forms;
use Livewire\Attributes\Validate;
use Livewire\Form;
use App\Models\Todo; // 假設你已經按照模組 2 建立過這個 Model
class TodoForm extends Form
{
// 使用 #[Validate] Attribute 定義驗證規則
#[Validate('required|min:3', message: '待辦事項最少需要 3 個字')]
public string $task = '';
public function store()
{
// 執行驗證
$this->validate();
// 模擬存入資料庫 (或實作 Model)
// Todo::create($this->all());
// 重設欄位
$this->reset();
}
}
第二步:實作 Todo List 元件 (結合實時驗證)¶
現在我們建立主元件來使用這個 Form Object。
1. 建立 TodoList 元件
php artisan make:livewire TodoList
2. 撰寫 Class (app/Livewire/TodoList.php)
namespace App\Livewire;
use Livewire\Component;
use App\Livewire\Forms\TodoForm;
class TodoList extends Component
{
// 注入 Form Object
public TodoForm $form;
// 模擬的待辦清單資料 (實務上會從資料庫抓)
public array $todos = [
['id' => 1, 'task' => '學習 PHP 8.3 Attribute 新語法', 'completed' => true],
['id' => 2, 'task' => '設定 Laravel 13 開發環境', 'completed' => false],
];
public function add()
{
// 呼叫 Form Object 的儲存邏輯
$this->form->store();
// 這裡僅作展示,實務上資料庫存完後會重新抓取 $this->todos
session()->flash('status', '任務已成功新增!');
}
public function toggle($id)
{
// 模擬切換完成狀態
foreach ($this->todos as &$todo) {
if ($todo['id'] === $id) {
$todo['completed'] = !$todo['completed'];
}
}
}
public function render()
{
return view('livewire.todo-list');
}
}
第三步:撰寫 View (使用 Flux UI 與實時驗證)¶
打開 resources/views/livewire/todo-list.blade.php。
這裡我們會用到 wire:model.blur(離開輸入框時驗證)或 wire:model.live(邊打邊驗證)。
<div class="p-6 bg-white rounded-xl shadow-sm border border-gray-100">
<flux:heading size="xl" class="mb-6">我的待辦清單</flux:heading>
@if (session('status'))
<div class="mb-4 p-3 bg-green-50 text-green-700 rounded-lg text-sm">
{{ session('status') }}
</div>
@endif
<form wire:submit="add" class="flex flex-col gap-2 mb-8">
<div class="flex gap-2">
<flux:input
wire:model.blur="form.task"
placeholder="輸入新任務..."
class="flex-grow"
/>
<flux:button type="submit" variant="primary">新增</flux:button>
</div>
@error('form.task')
<span class="text-red-500 text-xs">{{ $message }}</span>
@enderror
</form>
<ul class="space-y-3">
@foreach($todos as $todo)
<li class="flex items-center justify-between p-4 bg-gray-50 rounded-lg group">
<div class="flex items-center gap-3">
<flux:checkbox
wire:click="toggle({{ $todo['id'] }})"
:checked="$todo['completed']"
/>
<span class="{{ $todo['completed'] ? 'line-through text-gray-400' : 'text-gray-700' }}">
{{ $todo['task'] }}
</span>
</div>
<flux:button
variant="ghost"
icon="trash"
size="sm"
class="opacity-0 group-hover:opacity-100 transition-opacity"
/>
</li>
@endforeach
</ul>
</div>
第四步:驗證作法¶
- 將
<livewire:todo-list />加入你的儀表板頁面。 -
驗證實時驗證 (Real-time Validation):
- 在輸入框點擊一下,隨便打一個字,然後點擊頁面其他地方(觸發 blur 事件)。
- 你應該會立刻看到紅色的錯誤訊息:「待辦事項最少需要 3 個字」。這證明了驗證邏輯與 Form Object 運作成功。
-
驗證資料提交:
- 輸入超過 3 個字的內容,按下 Enter 或點擊「新增」按鈕。
- 輸入框應該會自動清空(因為
form->reset()),且上方會出現綠色的成功提示。
-
驗證交互:
- 點擊勾選方塊,確認任務文字是否會產生刪除線效果。
補充:依賴下拉選單 (Dependent Dropdowns) 小撇步¶
如果你需要實作「選擇縣市後自動載入行政區」,邏輯非常簡單:
- 在 Class 定義
public $selectedCity = null。 - 在 View 使用
wire:model.live="selectedCity"。 - 在 Class 使用 Computed Property 根據
$this->selectedCity回傳對應的行政區陣列。 - 當
selectedCity改變時,Livewire 會自動重新渲染行政區的<select>元件。
這就是模組 3 的第二部分。我們已經完成了 Form Objects、驗證、與 Todo List 實作。
準備好進入 第三部分 (事件系統、wire:navigate、檔案上傳與 Lazy Loading) 嗎?這一部分將讓你的 App 感覺更像是一個無縫的單頁應用 (SPA)!
JH Updated by Jeffery Hsu about 5 hours ago
這進入 模組 3 的第三部分(最終回)!這一部分包含的技術,會讓你的 Laravel 應用程式徹底擺脫傳統網頁的笨重感,擁有媲美 Vue/React 單頁應用 (SPA) 的流暢體驗,同時還能輕鬆處理檔案上傳與非同步載入。
我們將依序實作:SPA 導航、UX 狀態回饋、事件系統、檔案上傳,以及延遲載入 (Lazy Loading)。
第一步:SPA 體驗 (wire:navigate)¶
在過去,點擊連結換頁會讓整個瀏覽器畫面閃爍重新載入。Livewire 3 內建了強大的 wire:navigate,只需在 HTML 的 <a> 標籤加上一個屬性,就能讓頁面切換變成無縫的 SPA 體驗。
1. 實作與驗證
打開你的 resources/views/components/layouts/app.blade.php(或任何有導覽列的地方),修改你的連結:
<a href="/posts">文章列表</a>
<a href="/posts" wire:navigate class="text-blue-500 hover:underline">文章列表</a>
<a href="/dashboard" wire:navigate.hover class="text-blue-500 hover:underline">儀表板</a>
驗證:在瀏覽器點擊這些連結,你會發現網頁上方的進度條一閃而過,畫面瞬間切換,而且瀏覽器的重整圖示不會旋轉!
第二步:UX 增強 (wire:loading 與 wire:confirm)¶
在執行刪除或存檔等需要花點時間的動作時,給予使用者回饋是非常重要的。
1. 實作確認對話框與載入狀態
我們回到第二部分寫的 Todo List (resources/views/livewire/todo-list.blade.php),修改刪除按鈕的部分:
<flux:button
wire:click="delete({{ $todo['id'] }})"
wire:confirm="你確定要刪除這個任務嗎?這個動作無法復原!"
variant="danger"
size="sm"
>
刪除
</flux:button>
<span wire:loading wire:target="delete" class="text-sm text-gray-500 ml-2">
正在刪除中...
</span>
第三步:事件系統與元件溝通 (Events)¶
如果頁面上有兩個不相關的 Livewire 元件(例如:一個「購物車按鈕」和一個「商品列表」),當商品列表點擊「加入」時,如何讓購物車按鈕的數字更新?這就是事件系統的工作。
1. 觸發事件 (Dispatch)
在任何一個元件的 PHP 方法中,你可以廣播一個事件:
public function addToCart()
{
// ... 執行加入資料庫邏輯 ...
// 觸發一個名為 'cart-updated' 的事件
$this->dispatch('cart-updated');
}
2. 監聽事件 (Listen)
在另一個元件中(例如 CartIcon.php),使用 Laravel 13 支援的 PHP Attribute #[On] 來監聽:
namespace App\Livewire;
use Livewire\Component;
use Livewire\Attributes\On;
class CartIcon extends Component
{
public int $count = 0;
// 當接收到 'cart-updated' 事件時,執行此方法
#[On('cart-updated')]
public function updateCount()
{
$this->count++; // 或從資料庫重新抓取最新數量
}
public function render()
{
return view('livewire.cart-icon');
}
}
第四步:檔案上傳與即時預覽 (File Uploads)¶
Livewire 把檔案上傳變得不可思議的簡單,甚至內建了暫存與預覽機制。
1. 建立上傳元件
php artisan make:livewire ProfileUploader
2. 撰寫 Class (必須引入 WithFileUploads Trait)
打開 app/Livewire/ProfileUploader.php:
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithFileUploads;
use Livewire\Attributes\Validate;
class ProfileUploader extends Component
{
use WithFileUploads; // 必須加入這個 Trait 才能處理檔案
// 加上驗證規則:必須是圖片,最大 2MB
#[Validate('image|max:2048')]
public $photo;
public function save()
{
$this->validate();
// 將檔案儲存到 storage/app/public/photos 目錄下
$path = $this->photo->store('photos', 'public');
session()->flash('message', '大頭貼上傳成功!路徑:' . $path);
}
public function render()
{
return view('livewire.profile-uploader');
}
}
3. 撰寫 View (包含即時預覽)
打開 resources/views/livewire/profile-uploader.blade.php:
<div class="p-6 bg-white shadow rounded-lg max-w-sm">
<flux:heading size="lg" class="mb-4">更新大頭貼</flux:heading>
<form wire:submit="save">
<input type="file" wire:model="photo" class="mb-4">
@error('photo') <span class="text-red-500 text-sm block mb-2">{{ $message }}</span> @enderror
@if ($photo)
<div class="mb-4">
<p class="text-sm text-gray-500 mb-1">圖片預覽:</p>
<img src="{{ $photo->temporaryUrl() }}" class="w-32 h-32 object-cover rounded-full border">
</div>
@endif
<div class="flex items-center gap-4">
<flux:button type="submit" variant="primary">儲存圖片</flux:button>
<span wire:loading wire:target="photo" class="text-sm text-blue-500">正在上傳至暫存區...</span>
</div>
</form>
@if (session('message'))
<div class="mt-4 text-green-600 text-sm">{{ session('message') }}</div>
@endif
</div>
驗證:將 <livewire:profile-uploader /> 放進頁面測試。選取圖片後,你會看到旁邊出現「正在上傳」,接著立刻顯示圖片預覽(此時還沒正式存進資料庫),最後按下儲存才完成。
第五步:效能優化 (Lazy Loading 與 Polling)¶
1. Lazy Loading (延遲載入)
如果某個元件需要查詢超大資料庫,會拖慢整個頁面的初始載入時間。你可以加上 #[Lazy]。
use Livewire\Attributes\Lazy;
#[Lazy] // 加上這個,該元件就不會跟隨主頁面一起載入
class HeavyChart extends Component
{
public function render()
{
// 模擬耗時 2 秒的複雜查詢
sleep(2);
return view('livewire.heavy-chart');
}
// 在資料載入前,前端會顯示這個 Placeholder
public function placeholder()
{
return <<<'HTML'
<div class="p-4 text-center text-gray-500 animate-pulse">
載入複雜圖表中,請稍候...
</div>
HTML;
}
}
2. Polling (定期輪詢)
如果你需要實作「即時股價看板」或「系統監控」,只需在 HTML 加上 wire:poll:
<div wire:poll.2s>
目前伺服器 CPU 使用率:{{ $cpuUsage }} %
</div>
恭喜!到這裡為止,你已經完全掌握了 Livewire 3 的全貌,並且具備了開發高度互動式應用的能力。
前三個模組(環境架構、資料庫與 ORM、Livewire 反應式前端)是整個 Laravel 13 開發的最核心基石。接下來,我們將邁入 模組 4:進階與 API 開發(包含身分驗證、Sanctum API、Job 佇列與排程)。
你希望我們先複習一下前三個模組的內容,還是直接著手進行模組 4 呢?
JH Updated by Jeffery Hsu about 5 hours ago
- Status changed from 新建立 to 已解決
JH Updated by Jeffery Hsu about 4 hours ago
- Tracker changed from 功能 to 文件