不需要狀態管理,CustomEvent 就夠了。
用 islands 架構,一定會遇到這題:兩個互不相干的元件,要怎麼同步?
我的網站上,購物車狀態同時被三個地方用到:
它們是三個獨立掛載的元件,彼此看不到對方。客戶在商品頁按「加入購物車」,這三個地方都得更新。
很多人到這裡就開始找 Redux、Zustand、或是自己寫一個 store。
不用。瀏覽器本來就有事件系統。
購物車的每一次異動,都走同一個函式。成功之後,發一個事件:
window.dispatchEvent(new CustomEvent('cart:updated', { detail: data }));
想同步的人,自己訂閱:
window.addEventListener('cart:updated', (e) => {
const count = e.detail.count || 0;
badge.textContent = count;
badge.classList.toggle('hidden', count === 0);
});
就這樣。角標不知道抽屜的存在,抽屜不知道購物車頁的存在,它們只認識 cart:updated 這個名字。
想打開抽屜也一樣,發個事件就好:
window.elaceiteCart.open(); // 內部就是 dispatchEvent('cart:open')
因為島嶼之間的共用狀態很少。 我的整個網站只有兩個:購物車、還有 toast 通知。為了兩個狀態引進一套狀態管理函式庫,成本遠大於收益。
因為它不挑框架。 發事件的是一段普通的 <script>,接收的可以是 Preact 元件,也可以是一行原生 DOM 操作(角標就是)。不需要所有東西都活在同一個框架的世界裡。
因為它天然是單向的。 事件只能廣播,不能回傳。所以資料流永遠是「有人改了購物車 → 廣播 → 大家更新畫面」,不會變成元件之間互相呼叫的那種網。
事件的 detail 我一律帶完整的最新狀態,不是帶「剛剛改了什麼」。
detail: data // 後端回傳的完整購物車 payload
這樣訂閱者不需要自己累積狀態、不需要知道之前發生過什麼,拿到什麼就畫什麼。任何一個元件晚掛載、或是漏掉了一次事件,下一次事件來的時候會自動修正。
廣播完整狀態,不要廣播差異。 這是我在這裡學到最有用的一條。