Project

General

Profile

Actions

文件 #4

open
JH

模組 3:Livewire 3 核心概念(8–10 小時)

文件 #4: 模組 3:Livewire 3 核心概念(8–10 小時)

Added by Jeffery Hsu about 5 hours ago. Updated about 4 hours ago.

Status:
已解決
Priority:
正常
Assignee:
-
Start date:
04/26/2026
Due date:
% Done:

0%

Estimated time:

Description

  1. 安裝與 Livewire 3 介紹(全 PHP 反應式框架、與 Blade 完美整合)。
  2. 建立第一個 Livewire Component(Class + View)。
  3. 資料綁定(wire:model、Deferred vs Live、.live 修飾詞)。
  4. 屬性(Properties)、Computed Properties、Actions(Methods)。
  5. 表單處理(Validation、Real-time Validation、Form Objects)。
  6. 事件系統(dispatch()wire:navigate、Component Communication)。
  7. Lazy Loading、Polling、Wire:Loading、Wire:Confirm。
  8. 檔案上傳(Temporary Uploads、Preview、Validation)。
  9. 實作:Live Search、Counter、Todo List、Dependent Dropdowns。

JH Updated by Jeffery Hsu about 5 hours ago Actions #1

這個模組的內容非常豐富,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 />

第四步:驗證你的作法

  1. 確保伺服器 (php artisan serve) 正在運行。
  2. 回到瀏覽器,進入你的儀表板頁面。
  3. 驗證 Counter: 點擊加減號,確認畫面不閃爍即可更新數字。
  4. 驗證 Live Search: 在搜尋框中輸入 2,停頓一下,下方應該會立刻滑出 2317、2328、2330 的選項。接著輸入 廣宇2328,列表應該會精準過濾到只剩一筆。

如果以上都順利執行成功,就代表你已經完全掌握了 Livewire 3 的資料流、雙向綁定與即時反應機制。

準備好進入 模組 3 第二部分 (表單處理、驗證、Form Objects 與 Todo List 實作) 時,請隨時告訴我!

JH Updated by Jeffery Hsu about 5 hours ago Actions #2

這進入 模組 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>

第四步:驗證作法

  1. <livewire:todo-list /> 加入你的儀表板頁面。
  2. 驗證實時驗證 (Real-time Validation):
    • 在輸入框點擊一下,隨便打一個字,然後點擊頁面其他地方(觸發 blur 事件)。
    • 你應該會立刻看到紅色的錯誤訊息:「待辦事項最少需要 3 個字」。這證明了驗證邏輯與 Form Object 運作成功。
  3. 驗證資料提交:
    • 輸入超過 3 個字的內容,按下 Enter 或點擊「新增」按鈕。
    • 輸入框應該會自動清空(因為 form->reset()),且上方會出現綠色的成功提示。
  4. 驗證交互:
    • 點擊勾選方塊,確認任務文字是否會產生刪除線效果。

補充:依賴下拉選單 (Dependent Dropdowns) 小撇步

如果你需要實作「選擇縣市後自動載入行政區」,邏輯非常簡單:

  1. 在 Class 定義 public $selectedCity = null
  2. 在 View 使用 wire:model.live="selectedCity"
  3. 在 Class 使用 Computed Property 根據 $this->selectedCity 回傳對應的行政區陣列。
  4. selectedCity 改變時,Livewire 會自動重新渲染行政區的 <select> 元件。

這就是模組 3 的第二部分。我們已經完成了 Form Objects、驗證、與 Todo List 實作

準備好進入 第三部分 (事件系統、wire:navigate、檔案上傳與 Lazy Loading) 嗎?這一部分將讓你的 App 感覺更像是一個無縫的單頁應用 (SPA)!

JH Updated by Jeffery Hsu about 5 hours ago Actions #3

這進入 模組 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 Actions #4

  • Status changed from 新建立 to 已解決

JH Updated by Jeffery Hsu about 4 hours ago Actions #5

  • Tracker changed from 功能 to 文件
Actions

Also available in: PDF Atom