升級 Livewire V3 的踩坑心得

程式技術
sharkHead
升級 Livewire V3 的踩坑心得

萬眾期待的 Livewire V3 終於在前陣子推出了正式版本 (8/25),這個版本做了相當多更動,也加入了許多新功能。看了 Livewire 作者在 Laracon US 的介紹後,就非常期待正式版本的到來。

V3 剛推出 beta 的時候其實有著不少 🐛🐛🐛,幸好在作者與眾多 contributor 的努力下,目前 V3 已經相當穩定。

Livewire V3 最讓我期待的新功能就是 SPA Mode,只要在連結上加上 wire:navigate。點擊連結後不會重新整理頁面,而是直接更新現有的網頁內容,大大的增加了使用者體驗。

但在享受 V3 的 SPA mode 之前,按照慣例,升級還是有一些坑得踩,下面就簡單分享一下我的踩坑心得 😆。

詳細的升級流程請參考官方文件

在 SPA Mode 中,事件一註冊就會一直存在

在 V3 的 SPA mode 中,換頁並不會觸發瀏覽器的重新整理頁面,因此要注意的是 ...

在 SPA mode 中,事件一註冊就會一直存在

假設我用 V3 提供的事件 Livewire.hook("commit") 來做一些事情。

// 當 livewire 更新 DOM 之後就會觸發,等同於 v2 的 'message.processed' 事件
Livewire.hook('morph.updated', ({ component }) => {
    console.log("hello");
})

接下來打開瀏覽器的 dev tools,你會發現每次 livewire 一更新完 DOM,console 都會跟你 say hello。

如果你只想要讓事件只在特定頁面觸發,就要特別處理,例如我只想要在 say-hello-page 這個 component 中觸發事件。

Livewire.hook('morph.updated', ({ component }) => {
    // 只在 'say-hello-page' 頁面觸發
    if (component.name === 'say-hello-page') {
        console.log("hello");
    }
})

在 SPA Mode 中,JavaScript 的載入與執行

一般來說在 v2 ,我們可能會這樣使用第三方的前端套件。

Tagify 為例,首先建立一個 resources/ts/tagify.ts 檔案。

// 載入 tagify
import Tagify from "@yaireo/tagify";

// 尋找要套用 tagify 的 input element
let tagsInput: InputElement = document.setElementById("tags");

// 如果 input element 存在,就套用 tagify
if (tagsInput) {
  new Tagify(tagsInput);
}

然後在 livewire 的 component 中使用 @vite('resources/ts/tagify.ts') 載入 tagify.ts

<div>
  <!-- tagify 會在 input 的前方加上一個新的元素 -->
  <!-- 為了避免 component 更新後刪除新的元素,可以使用 wire:ignore -->
  <div wire:ignore>
    <input type="text" id="tags" />
  </div>

  <!-- 會轉換成 <script> 標籤載入 tagify -->
  @vite('resources/ts/tagify.ts')
</div>

很常見的載入方式,但如果換到 V3 的 SPA mode 就會有問題。

因為在 V3 的機制中,<body> 內的 <script> 是會重複執行的

假設你離開頁面後再重新進入頁面,tagify.ts 就會再執行一次。

重新載入一大包 Tagify,這聽起來就不是效能很好的做法 😂。

因此我們應該這麼做,不在 tagify.ts 執行套用的動作,單純只做載入

// 載入 tagify
import Tagify from "@yaireo/tagify";

// typescript 比較囉唆點,所以要先宣告一下我們想在 window 物件中放的東西
declare global {
  interface Window {
    Tagify: any;
  }
}

// 因為 @vite 是使用模組化的方式載入,任何變數都無法在外部使用。
// 所以我們要將 tagify 放到 window 物件中
window.Tagify = Tagify;

然後將 @vite('resources/ts/tagify.ts') 放在 <head> 中載入。

在 V3 的機制中,<head> 中的 <script> 只會執行一次,除非你用瀏覽器重新整理頁面

<head>
  <!-- ... -->
  @vite('resources/ts/tagify.ts')
</head>

然後在 component 中進行 Tagify 套用的動作,這樣就不會重複載入整個 Tagify 了。

<div>
  <div wire:ignore>
    <input type="text" id="tags" />
  </div>

  <script>
    let tagsInput: InputElement = document.setElementById("tags");

    if (tagsInput) {
      new Tagify(tagsInput);
    }
  </script>
</div>

因為 V3 底層改為使用 alpine.js,你也可以考慮使用 alpine.js 的語法

<div
  x-data
  x-init="
    new Tagify($refs.tags);
  "
>
  <div wire:ignore>
    <input type="text" x-ref="tags" />
  </div>
</div>

離開頁面後,按上一頁。有兩個 Tagify 元素 !?

如剛剛提到的,Tagify 會在你的 <input> 前面加上一個新的元素。

<div wire:ignore>
  <!-- tagify.ts 會新增一個新的元素 -->
  <tags class="tagify tagify--noTags tagify--empty" tabindex="-1"> ... </tags>

  <input type="text" x-ref="tags" />
</div>

V3 預設會 cache 你訪問過的頁面。假設你離開頁面後再按上一頁,V3 會直接使用 cache 的頁面, 而且 <body> 內的 <script> 會再執行一次。

注意!cache 是儲存經過 javascript 渲染後的頁面

也就是說,如果你離開了有 Tagify 頁面,然後在按上一頁返回。這時候如果 tagify.ts 再執行一次會發生什麼事情呢?

沒錯...,你會發現畫面上有兩個 Tagify 元素 😂。

<div wire:ignore>
  <!-- 剛剛 tagify.ts 新增的元素 -->
  <tags class="tagify tagify--noTags tagify--empty" tabindex="-1"> ... </tags>

  <!-- 離開頁面後,點選上一頁重新回來,tagify.ts 又會新增一個新的元素 -->
  <tags class="tagify tagify--noTags tagify--empty" tabindex="-1"> ... </tags>

  <input type="text" x-ref="tags" />
</div>

如果是重新點擊含有 wire:navigate 的連結進入頁面,就不會有這個問題。

這個問題在 beta 版本就有人提出來了。而作者也很快的新增了一個 livewire:navigating event 來做處理。

livewire:navigating 讓你可以在離開頁面時,對即將要被 cache 的頁面做一些處理。

document.addEventListener("livewire:navigating", () => {
  // Mutate the HTML before the page is navigated away...
});

因此我們要做的,就是在離開頁面前,移除掉 tagify.ts 新增的元素。讓我們稍微修改一下套用 Tagify 的程式碼。

let tagify = new Tagify(tagsInput);


let removeTagify = () => {
  // 移除 Tafigy 元素
  tagify.destroy();
  // 移除 livewire:navigating 事件監聽,避免離開頁面後持續執行 removeTagify
  document.removeEventListener('livewire:navigating', removeTagify);
};

// 註冊 livewire:navigating 事件監聽,一離開頁面就把 Tafigy 元素移除
document.addEventListener("livewire:navigating", removeTagify);

小結

可以發現在 SPA 模式底下,JavaScript 的使用方式明顯有別於 SSR 模式。本次升級我花了不少時間釐清 JavaScript 的特性,希望這篇能幫助到同樣想升級 Livewire V3 的你 😁。

sharkHead
written by
sharkHead

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

1 則留言
sharkHead sharkHead 2023 年 11 月 18 日 (已編輯)

升級 v3 後,原本用的 Google reCAPTCHA 也有遇到坑。

索性換成了 Cloudflare Turnstile 😎。