跳至主要內容
Skip to content

跨分頁即時通訊:Broadcast Channel API 實戰

在 Web 開發中,我們常遇到一個棘手問題:使用者在「分頁 A」登出了,但在「分頁 B」卻還顯示登入狀態,導致後續操作失敗。或者使用者在一個分頁切換了「深色模式」,其他分頁卻沒有同步跟進。

Broadcast Channel API 正是為此而生。它允許我們在 同源 (Same-Origin) 的不同瀏覽上下文(分頁、視窗、iframe、Worker)之間建立一個廣播頻道,實現簡單高效的一對多通訊。


一、 API 核心概念

1.1 基本語法

使用方式就像收音機頻道一樣:

javascript
// A 分頁:建立頻道並發送訊息
const channel = new BroadcastChannel('app_channel');
channel.postMessage({ type: 'LOGOUT' });

// B 分頁:加入同一頻道並接收訊息
const channel = new BroadcastChannel('app_channel');
channel.onmessage = (event) => {
  if (event.data.type === 'LOGOUT') {
    console.log('收到登出指令');
  }
};

1.2 與其他方案比較

方案特點缺點
Broadcast Channel專為通訊設計,API 簡單,延遲極低。不支援舊版瀏覽器 (IE)。
localStorage透過 storage 事件監聽。語意不明確,儲存容量有限制。
SharedWorker可共享狀態與邏輯。實作複雜度高。

二、 封裝 useBroadcastChannel Composable

我們將封裝一個 useBroadcastChannel,讓訊息的收發變得具備響應式特性,並自動管理資源釋放。

2.1 介面定義

typescript
export interface UseBroadcastChannelReturn<T> {
  isSupported: boolean;
  data: Ref<T | null>;
  post: (data: T) => void;
  close: () => void;
  error: Ref<MessageEvent | null>;
}

2.2 完整實作

typescript
// composables/useBroadcastChannel.ts
import { ref, onMounted, onUnmounted } from 'vue';

export function useBroadcastChannel<T = any>(name: string) {
  const isSupported = typeof window !== 'undefined' && 'BroadcastChannel' in window;
  
  const data = ref<T | null>(null);
  const error = ref<MessageEvent | null>(null);
  
  let channel: BroadcastChannel | null = null;

  const post = (msg: T) => {
    if (channel) {
      channel.postMessage(msg);
    }
  };

  const close = () => {
    if (channel) {
      channel.close();
      channel = null;
    }
  };

  onMounted(() => {
    if (isSupported) {
      channel = new BroadcastChannel(name);
      
      channel.onmessage = (event) => {
        data.value = event.data;
      };

      channel.onmessageerror = (event) => {
        error.value = event;
        console.error('BroadcastChannel Error:', event);
      };
    }
  });

  onUnmounted(() => {
    close();
  });

  return {
    isSupported,
    data,
    post,
    close,
    error
  };
}

三、 實戰範例:全域登出同步

這是 Broadcast Channel 最經典的應用場景。當使用者在任何一個分頁登出時,其他分頁應該自動跳轉回登入頁面。

3.1 登出控制元件 (LogoutButton.vue)

vue
<script setup lang="ts">
import { useBroadcastChannel } from '@/composables/useBroadcastChannel';

// 定義通訊格式
interface AuthMessage {
  type: 'LOGIN' | 'LOGOUT';
  payload?: any;
}

const { post } = useBroadcastChannel<AuthMessage>('auth_channel');

const handleLogout = () => {
  // 1. 執行本地登出邏輯 (清除 Token 等)
  localStorage.removeItem('token');
  
  // 2. 通知其他分頁
  post({ type: 'LOGOUT' });
  
  // 3. 本頁跳轉
  window.location.href = '/login';
};
</script>

<template>
  <button @click="handleLogout">登出</button>
</template>

3.2 應用程式根元件 (App.vue)

vue
<script setup lang="ts">
import { watch } from 'vue';
import { useBroadcastChannel } from '@/composables/useBroadcastChannel';

interface AuthMessage {
  type: 'LOGIN' | 'LOGOUT';
}

const { data } = useBroadcastChannel<AuthMessage>('auth_channel');

// 監聽來自其他分頁的訊息
watch(data, (msg) => {
  if (msg?.type === 'LOGOUT') {
    alert('您已在其他分頁登出,即將返回登入頁面。');
    window.location.href = '/login';
  }
});
</script>

四、 複雜度更低的狀態同步

除了強制登出,我們也可以用來同步「應用程式狀態」,例如:當使用者在列表頁新增一筆資料,詳情頁可以即時收到通知並重新整理。

typescript
// 列表頁
post({ type: 'REFRESH_LIST' });

// 詳情頁 / 其他列表頁
watch(data, (msg) => {
  if (msg?.type === 'REFRESH_LIST') {
    fetchData(); // 重新抓取 API
  }
});

總結

BroadcastChannel 是一個輕量且高效的 API,它極大地簡化了同源分頁間的通訊邏輯。透過 Vue Composable 的封裝,我們能以宣告式的方式處理跨視窗狀態,讓使用者體驗更加無縫與一致。

Web API 系列文章至此暫告一段落。從語音互動到硬體控制,再到系統深層整合,希望這 16 篇文章能幫助你挖掘瀏覽器的無限潛能!


延伸閱讀