談談 Livewire 的 Json Attribute

談談 Livewire 的 Json Attribute
程式技術

Livewire 4 在前幾天終於正式發佈啦!這次同樣帶來了翻天覆的的改變 😆,最大的亮點莫過於 SFC(Single-file Component) 了。現在你可以將前後端的邏輯都寫在同一個檔案中,讓開發體驗更接近現代化的前端框架。

<?php

use Livewire\Component;

new class extends Component
{
    // 這裡可以寫後端邏輯
};
?>

<div>
    {{-- 這裡寫前端模板 --}}
</div>

如果你還是習慣將前後端邏輯分成兩個檔案,Livewire 4 依然支援 MFC(Multi-file Component)的模式。

雖然 Livewire 4 正式版最近才上線,但早在去年還在 Beta 階段的時候,我就已經將自己的部落格升級到 4 了,並幫忙回報一些 Bug 🐛。正式版除了原先 Beta 版有的新功能,還加入了一些 Beta 版沒有的新功能,其中最讓我眼前一亮的,就是新的 #[Json] Attribute。

下面是官方文件示範如何使用 #[Json] Attribute 實作常見的文章搜尋功能:

<?php

declare(strict_types=1);

use App\Models\Post;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Attributes\Json;
use Livewire\Component;

new class extends Component
{
    // 將這個方法標註為 json endpoint
    // 你可以在前端使用 $wire.search() 呼叫這個方法,並取得回傳值
    // [!code highlight:1]
    #[Json]
    public function search(string $query): Collection
    {
        // json endpoint 的回傳值會被轉為前端可以直接使用的 json 格式
        return Post::search($query)
            ->take(10)
            ->get();
    }
};
?>

@script
<script>
    // 定義一個 alpine component
    Alpine.data('searchComponent', () => ({
        query: '',
        posts: [],
        onInput() {
            // [!code highlight:5]
            // 呼叫後端的 search() 方法取得文章搜尋結果
            $wire.search(this.query)
                // 回傳的結果為 Promise
                // 可以使用 then() 或是 await 取得 JSON 格式的資料
                .then(data => this.posts = data)
        }
    }));
</script>
@endscript

{{-- 使用剛剛定義的 alpine component --}}
<div x-data="searchComponent">
    <input
        type="text"
        x-model="query"
        x-on:input.debounce="onInput"
        placeholder="搜尋文章..."
    >

    <ul>
        {{-- 使用迴圈顯示文章搜尋結果 --}}
        <template x-for="post in posts" :key="post.id">
            <li x-text="post.title"></li>
        </template>
    </ul>
</div>

簡單來說,#[Json] Attribute 可以將 Livewire 組件中的方法變為 JSON 端點(JSON Endpoint)。當你在前端透過 $wire 調用此方法時,Livewire 會直接回傳用 Promise 包裝好的 JSON 格式資料,而且不會觸發組件的重新渲染(Re-rendering)流程。

有點像是 SvelteKit 的 Remote Function 功能。前端可以呼叫後端的方法並取得資料。

如果你的方法中有使用到 Validation 的話,那麼在 Validation 失敗的時候,Livewire 也會回傳 JSON 格式的錯誤訊息。

{
    "status": 422,    // HTTP 狀態碼 (validation 失敗會回傳 422)
    "body": null,     // 原始回應的內容 (validation 失敗會回傳 null)
    "json": null,     // JSON 格式的回應 (validation 失敗會回傳 null)
    "errors": {...}   // Validation 的錯誤訊息
}

你可以使用 catch() 來接收錯誤訊息並進行對應的處理。

$wire.save()
    .then((data) => {
      // 處理成功回應
      console.log(data);
    })
    .catch((e) => {
      if (e.status === 422) {
        // 處理 validation 失敗
        console.log(e.errors);
      }
    });

可以看到掛上 #[Json] Attribute 後,前端與後端交互方式就會變成我們很熟悉的模式 - 使用 JSON 格式的資料進行交互

提升 Livewire 效能的方式,就是不使用 Livewire!?

#[Json] Attribute 之所以讓我眼前一亮,是因為近年來我在關注 Livewire 相關新聞時,有注意到一個關於優化 Livewire 的趨勢。實際上,隨著 Livewire 的普及,關於它的優缺點與效能優化的討論也越來越多。例如這篇在 Reddit 上關於 Livewire 優缺點的討論中,有一位開發者的建議獲得了廣泛認同。

他提到,如果你的畫面上有一個區塊需要根據狀態頻繁切換顯示,盡量不要這樣寫:

@if ($isEnabled)
    <div>content here</div>
@endif;

因為這會觸發 Livewire 組件的更新頁面流程。他建議使用 Alpine.js 的 x-show,效能會好得多:

<div x-cloak x-show="$wire.isEnabled">
    content here
</div>

Laravel 的開發者關係工程師 Josh Cirre,在看到這個討論串後有製作一部影片分享了自己的看法。

他提到,任何工具都有適合與不適合的場景,連 Livewire 也不例外。雖然 Livewire 3 透過合併同一頁面上的多個組件的請求,大幅度減少向伺服器發送請求的次數,但是如果連細微的 UI 互動都要依賴伺服器端的狀態,那麼這種使用的數量一多,就很難不影響應用的效能。

在影片的留言區中,Filament 的開發者關係工程師 Alex Six 提到,他們為了提升 Filament 4 的效能花了很多心力,而核心的策略就是:盡可能的使用 Alpine.js

無獨有偶,Laravel Cloud 的核心開發者 Ryan Chandler,也在去年的 Wire Live 議程中分享如何在開發 Livewire 應用時充分利用 Alpine.js 執行前端渲染來提升效能。

這些效能的改善的建議背後都有一個共同點,那就是:

盡量避免 Livewire 組件重新渲染頁面。

原因很簡單,一旦觸發 Livewire 組件的更新頁面流程,伺服器回應的 Payload 就會包含一大段更新後的 HTML:

{
  "components": [
    {
      "snapshot": "...",
      "effects": {
        "returns": [null],
        // [!code highlight:1]
        "html": "一大片更新後的 HTML..."
      }
    }
  ],
  "assets": []
}

當互動頻率高或 DOM 結構複雜時,相較於單純的 JSON 資料,這種包含大量 HTML 的 Payload 顯然會造成較大的效能負擔。

魚與熊掌可以兼得,#[Json] 的妙用

官方文件對於使用 #[Json] 的建議如下:

  • 實作搜尋建議 (Autocomplete)。
  • 為前端圖表或第三方套件加載動態數據。
  • 任何只需要數據而不需要更新 HTML 結構的互動。

但我感覺用途遠不只有這些,有一種比較極端的用法,任何需要跟後端取得資料的地方,都使用 #[JSON] Attribute,前端只跟後端拿簡易的 JSON 格式資料,並使用 Alpine.js 根據資料渲染頁面。藉由不觸發 Livewire 重新渲染,把效能最大化。這樣的使用方式就好像我們熟悉的純 API + 前端框架的模式一般。

但仔細一想,這樣好像就喪失了 Livewire 最大的魅力,也就是用 Laravel 的方式去開發前端,既不用煩惱前端該如何與後端進行資料交換,也不用煩惱該如何更新前端頁面。

我覺得 #[Json] Attribute 出現的意義,就是它能讓我們在 Livewire 組件內定義「輕量級的 API 端點」。讓我們在不需要重新渲染頁面的前提下,更方便的與後端進行資料上的交換,同時也不需要特地去寫一支獨立的 API Controller。

總結來說,#[Json] 讓 Livewire 組件能更靈活的的在「伺服器端渲染」與「客戶端渲染」之間切換,是開發高效能 Livewire 應用的新利器。

參考資料

Allen
written by
Allen

持續努力中的後端打工仔,在下班後喜歡研究各種不同的技術。稍微擅長 PHP,並偶爾涉獵前端開發。個性就像動態語言般隨興,但渴望做事能像囉嗦的靜態語言那樣嚴謹。

則留言
顯示更多留言
新增留言
訪客 2026 年 02 月 07 日