Supabase Realtime 实时订阅
到目前为止,前端数据都是靠请求拉取,页面不会自己变。这一篇学习 Realtime,让数据库发生变化时,页面自动收到推送,不刷新也更新。
Realtime 是什么
Realtime 是数据库事件的实时推送服务。它建立一条长期连接的通道,当表里发生插入、更新、删除时,服务器立刻把变化推给所有订阅者。
体验上就是,A 用户在聊天室发了一条消息,B 用户的页面几乎同时出现这条消息,全程没有刷新,没有轮询。
轮询与推送
实现”自动更新”有两种思路
| 方式 | 原理 | 特点 |
|---|---|---|
| 轮询 | 每隔几秒主动问一次 | 简单但浪费请求,有延迟 |
| 推送 | 变化发生时服务器通知 | 实时,省流量,体验好 |
Realtime 基于 WebSocket 技术实现推送,这也是它区别于传统后端的一大亮点。在线聊天、协同编辑、通知中心,都是 Realtime 的典型场景。
开启 Realtime
新建的表默认不广播变化,需要先在 Dashboard 开启。进入 Realtime 菜单,找到你的表,打开 Database changes 下的 INSERT、UPDATE、DELETE 开关。
数据库变化通过 PostgreSQL 的 publication 机制对外发布,这个开关本质就是创建 publication,后台配置一次即可,前端无需额外开启。
订阅表变化
代码模型非常固定,一个 channel 里注册监听器,然后订阅
const channel = supabase
.channel('posts-list')
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'posts'
},
(payload) => {
console.log('新文章', payload.new)
}
)
.subscribe()
| 参数 | 含义 |
|---|---|
| channel 名称 | 这条通道的标识,可以自定义 |
| event | INSERT、UPDATE、DELETE,不写表示监听全部 |
| schema | 默认 public |
| table | 要监听的表名 |
| payload.new | 变化后的数据,删除事件里是 payload.old |
监听全部事件的写法
.on(
'postgres_changes',
{ schema: 'public', table: 'posts' },
(payload) => {
if (payload.eventType === 'INSERT') { /* 新数据 */ }
if (payload.eventType === 'UPDATE') { /* payload.new 是新值 */ }
if (payload.eventType === 'DELETE') { /* payload.old 是旧值 */ }
}
)
在线聊天室极简版
用实时订阅做一个多人聊天室,完整感受实时效果。先建消息表并配置 RLS,任何人都能读和发
create table public.messages (
id bigint generated always as identity primary key,
content text not null,
user_name text,
created_at timestamptz default now()
);
alter table public.messages enable row level security;
create policy "消息公开读"
on public.messages for select
using (true);
create policy "登录用户可发消息"
on public.messages for insert
with check (auth.role() = 'authenticated');
前端组件订阅新消息并渲染
<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { supabase } from '@/lib/supabase'
const messages = ref([])
const input = ref('')
let channel
onMounted(async () => {
// 先拉一次历史消息
const { data } = await supabase
.from('messages')
.select('*')
.order('created_at', { ascending: false })
.limit(20)
messages.value = data || []
// 订阅新消息
channel = supabase
.channel('chat-room')
.on(
'postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'messages' },
(payload) => {
messages.value.unshift(payload.new)
}
)
.subscribe()
})
onUnmounted(() => {
supabase.removeChannel(channel) // 离开页面时关闭订阅
})
async function send() {
const { data: { user } } = await supabase.auth.getUser()
await supabase.from('messages').insert({
content: input.value,
user_name: user.user_metadata.user_name || '匿名'
})
input.value = ''
}
</script>
<template>
<ul>
<li v-for="m in messages" :key="m.id">
{{ m.user_name }}:{{ m.content }}
</li>
</ul>
<input v-model="input" placeholder="说点什么" />
<button @click="send">发送</button>
</template>
自己发送的消息不用手动插入列表,insert 触发数据库变化,订阅回调会自动把它加进列表。两条浏览器窗口并排打开,一边发消息另一边立刻出现,实时效果一目了然。
清理订阅
页面销毁时记得调用 removeChannel 释放连接,否则打开关闭页面反复操作会积累一堆无效通道。组件卸载时清理是标准写法。
Realtime 与 RLS
订阅推送同样受 RLS 约束。没有权限读某行的用户,收不到这行的变化推送。所以前面给 messages 表配了公开读策略,聊天室才能人人可见。设计订阅时先想清楚目标用户的读权限。
广播功能
postgres_changes 是数据库变化的订阅,Realtime 还支持纯广播,不经过数据库,直接给通道内其他客户端发消息,适合光标位置、正在输入这类瞬时状态
channel.send({
type: 'broadcast',
event: 'cursor',
payload: { x: 100, y: 200 }
})
同类客户端用 channel.on(‘broadcast’, { event: ‘cursor’ }, cb) 接收。广播不做持久化,刷新页面就没了,与 postgres_changes 是互补关系。
最后一篇把前十一篇的内容串成一个完整的小项目。
下一篇 Supabase 综合实战