聊天室是一個私密的廣播電台頻道。只有知道正確頻率(例如:訂單ID)的人才能收聽和發話。Supabase Realtime 提供建立這種私密頻道的能力,讓 App 使用者之間可以即時溝通。
為了讓程式碼更容易管理,會將所有跟聊天相關的複雜邏輯,都放進一個叫做 useRealtimeChat
的 Hook 檔案。未來任何頁面需要聊天功能時,只要引用這個檔案,而不需要重複撰寫一樣的程式碼。
目標:
同時處理「監聽頻道」和「發送訊息」兩大功能。
// useRealtimeChat.ts
// 定義 ChatMessage 的結構
export interface ChatMessage {
id: string
content: string
user: {
name: string
}
createdAt: string
}
// 為廣播的事件取一個固定的名字
const EVENT_MESSAGE_TYPE = 'message'
/**
* @param roomName - 聊天的頻道名稱,我們會用訂單ID來當作頻道名稱
* @param username - 使用者的名稱
*/
export function useRealtimeChat({ roomName, username }: { roomName: string, username: string }) {
const supabase = createClient()
const [messages, setMessages] = useState<ChatMessage[]>([])
const [channel, setChannel] = useState<ReturnType<typeof supabase.channel> | null>(null)
const [isConnected, setIsConnected] = useState(false)
//「建立頻道連線」與「監聽訊息」
useEffect(() => {
// 1. 根據傳入的 roomName,建立一個新的頻道
const newChannel = supabase.channel(roomName)
// 2. 設定這個頻道要監聽的事件
newChannel
.on('broadcast', { event: EVENT_MESSAGE_TYPE }, (payload) => {
// 當收到廣播訊息時,將新訊息加到目前的訊息列表中
setMessages((current) => [...current, payload.payload as ChatMessage])
})
.subscribe(async (status) => {
// 3. 正式訂閱頻道,開始接收訊息
if (status === 'SUBSCRIBED') {
// 如果成功訂閱,就把連線狀態設為 true
setIsConnected(true)
}
})
// 4. 將建立好的頻道物件存起來,方便後續使用
setChannel(newChannel)
// 5. 當元件被銷毀時(例如:使用者離開聊天室頁面),要記得移除頻道連線,節省資源
return () => {
supabase.removeChannel(newChannel)
}
}, [roomName, username, supabase])
// 「發送訊息」
const sendMessage = useCallback(
async (content: string) => {
// 如果頻道不存在或尚未連線成功,就不做任何事
if (!channel || !isConnected) return
// 準備要發送的訊息物件
const message: ChatMessage = {
id: crypto.randomUUID(),
content,
user: {
name: username,
},
createdAt: new Date().toISOString(),
}
// 核心流程
// 步驟 1: 將訊息儲存到資料庫
const { error } = await supabase.from('messages').insert({
id: message.id,
room_id: roomName, // 紀錄這個訊息是屬於哪個聊天室的
user_name: message.user.name,
content: message.content,
created_at: message.createdAt,
})
// 步驟 2: 透過頻道廣播訊息給所有在線上的使用者
await channel.send({
type: 'broadcast',
event: EVENT_MESSAGE_TYPE,
payload: message, // 廣播的內容就是我們剛剛建立的訊息物件
})
},
[channel, isConnected, username, roomName, supabase]
)
// 將「訊息列表」、「發送訊息的函式」、「連線狀態」回傳給使用這個 Hook 的頁面
return { messages, sendMessage, isConnected }
}
透過 useRealtimeChat
Hook,我們成功地將複雜的邏輯封裝起來。
未來在任何需要聊天功能的頁面,我們只需要簡單地呼叫 useRealtimeChat
,就能馬上獲得 messages
列表來顯示,以及一個 sendMessage
函式來發送訊息。
... to be continued
有任何想討論歡迎留言,或需要指正的地方請鞭大力一點,歡迎訂閱、按讚加分享,分享給想要提升開發效率的朋友