Supabase数据库变更监听 #

一、启用表监听 #

1.1 Dashboard启用 #

text
Dashboard > Database > Replication

步骤
├── 1. 找到目标表
├── 2. 开启Supabase Realtime
└── 3. 保存

1.2 SQL启用 #

sql
-- 启用单个表
ALTER PUBLICATION supabase_realtime ADD TABLE messages;

-- 启用多个表
ALTER PUBLICATION supabase_realtime ADD TABLE messages, users, posts;

-- 查看已启用的表
SELECT schemaname, tablename 
FROM pg_publication_tables 
WHERE pubname = 'supabase_realtime';

二、监听所有变更 #

2.1 监听所有事件 #

typescript
const channel = supabase
  .channel('all-changes')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'messages',
    },
    (payload) => {
      const { eventType, new: newRecord, old: oldRecord } = payload
      
      switch (eventType) {
        case 'INSERT':
          console.log('New record:', newRecord)
          break
        case 'UPDATE':
          console.log('Updated from:', oldRecord)
          console.log('Updated to:', newRecord)
          break
        case 'DELETE':
          console.log('Deleted:', oldRecord)
          break
      }
    }
  )
  .subscribe()

三、监听INSERT事件 #

3.1 基础监听 #

typescript
const channel = supabase
  .channel('inserts')
  .on(
    'postgres_changes',
    {
      event: 'INSERT',
      schema: 'public',
      table: 'messages',
    },
    (payload) => {
      console.log('New message:', payload.new)
    }
  )
  .subscribe()

3.2 聊天应用示例 #

tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'

interface Message {
  id: number
  content: string
  user_id: string
  created_at: string
}

export function ChatRoom({ roomId }: { roomId: string }) {
  const [messages, setMessages] = useState<Message[]>([])

  useEffect(() => {
    // 加载历史消息
    loadMessages()

    // 监听新消息
    const channel = supabase
      .channel(`room-${roomId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `room_id=eq.${roomId}`,
        },
        (payload) => {
          setMessages((prev) => [...prev, payload.new as Message])
        }
      )
      .subscribe()

    return () => {
      channel.unsubscribe()
    }
  }, [roomId])

  async function loadMessages() {
    const { data } = await supabase
      .from('messages')
      .select('*')
      .eq('room_id', roomId)
      .order('created_at', { ascending: true })

    setMessages(data || [])
  }

  async function sendMessage(content: string) {
    await supabase.from('messages').insert({
      room_id: roomId,
      content,
      user_id: (await supabase.auth.getUser()).data.user?.id,
    })
  }

  return (
    <div>
      <div className="messages">
        {messages.map((msg) => (
          <div key={msg.id}>{msg.content}</div>
        ))}
      </div>
      <input
        onKeyDown={(e) => {
          if (e.key === 'Enter') {
            sendMessage(e.currentTarget.value)
            e.currentTarget.value = ''
          }
        }}
      />
    </div>
  )
}

四、监听UPDATE事件 #

4.1 基础监听 #

typescript
const channel = supabase
  .channel('updates')
  .on(
    'postgres_changes',
    {
      event: 'UPDATE',
      schema: 'public',
      table: 'users',
    },
    (payload) => {
      console.log('Before:', payload.old)
      console.log('After:', payload.new)
    }
  )
  .subscribe()

4.2 监听特定字段变化 #

typescript
// 使用触发器记录字段变化
// 在数据库中创建
CREATE OR REPLACE FUNCTION notify_status_change()
RETURNS TRIGGER AS $$
BEGIN
    IF OLD.status IS DISTINCT FROM NEW.status THEN
        PERFORM pg_notify(
            'status_changed',
            json_build_object(
                'id', NEW.id,
                'old_status', OLD.status,
                'new_status', NEW.status
            )::text
        );
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

五、监听DELETE事件 #

5.1 基础监听 #

typescript
const channel = supabase
  .channel('deletes')
  .on(
    'postgres_changes',
    {
      event: 'DELETE',
      schema: 'public',
      table: 'messages',
    },
    (payload) => {
      console.log('Deleted message:', payload.old)
      // payload.old 包含被删除记录的数据
    }
  )
  .subscribe()

5.2 实时删除UI元素 #

tsx
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'

export function TodoList() {
  const [todos, setTodos] = useState<any[]>([])

  useEffect(() => {
    loadTodos()

    const channel = supabase
      .channel('todos-changes')
      .on('postgres_changes', {
        event: 'INSERT',
        schema: 'public',
        table: 'todos',
      }, (payload) => {
        setTodos((prev) => [...prev, payload.new])
      })
      .on('postgres_changes', {
        event: 'DELETE',
        schema: 'public',
        table: 'todos',
      }, (payload) => {
        setTodos((prev) => prev.filter(t => t.id !== payload.old.id))
      })
      .on('postgres_changes', {
        event: 'UPDATE',
        schema: 'public',
        table: 'todos',
      }, (payload) => {
        setTodos((prev) => 
          prev.map(t => t.id === payload.new.id ? payload.new : t)
        )
      })
      .subscribe()

    return () => channel.unsubscribe()
  }, [])

  async function loadTodos() {
    const { data } = await supabase.from('todos').select('*')
    setTodos(data || [])
  }

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

六、过滤监听 #

6.1 按列值过滤 #

typescript
// 只监听特定用户的变更
const channel = supabase
  .channel('user-todos')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'todos',
      filter: 'user_id=eq.user-123',
    },
    (payload) => {
      console.log('User todo changed:', payload)
    }
  )
  .subscribe()

6.2 多条件过滤 #

typescript
// 组合过滤
const channel = supabase
  .channel('filtered')
  .on(
    'postgres_changes',
    {
      event: '*',
      schema: 'public',
      table: 'orders',
      filter: 'status=eq.pending AND total=gt.1000',
    },
    (payload) => {
      console.log('High value pending order:', payload)
    }
  )
  .subscribe()

七、Payload结构 #

7.1 INSERT Payload #

typescript
{
  commit_timestamp: '2024-01-15T10:30:00Z',
  eventType: 'INSERT',
  schema: 'public',
  table: 'messages',
  new: {
    id: 1,
    content: 'Hello',
    user_id: 'user-123',
    created_at: '2024-01-15T10:30:00Z'
  },
  old: {},
  errors: null
}

7.2 UPDATE Payload #

typescript
{
  commit_timestamp: '2024-01-15T10:30:00Z',
  eventType: 'UPDATE',
  schema: 'public',
  table: 'messages',
  new: {
    id: 1,
    content: 'Hello Updated',
    user_id: 'user-123',
    created_at: '2024-01-15T10:30:00Z'
  },
  old: {
    id: 1,
    content: 'Hello',
    user_id: 'user-123',
    created_at: '2024-01-15T10:30:00Z'
  },
  errors: null
}

7.3 DELETE Payload #

typescript
{
  commit_timestamp: '2024-01-15T10:30:00Z',
  eventType: 'DELETE',
  schema: 'public',
  table: 'messages',
  new: {},
  old: {
    id: 1,
    content: 'Hello',
    user_id: 'user-123',
    created_at: '2024-01-15T10:30:00Z'
  },
  errors: null
}

八、最佳实践 #

8.1 连接管理 #

typescript
// 使用自定义Hook管理订阅
function useRealtimeSubscription(
  table: string,
  event: 'INSERT' | 'UPDATE' | 'DELETE' | '*',
  callback: (payload: any) => void,
  filter?: string
) {
  useEffect(() => {
    const channel = supabase
      .channel(`${table}-${event}`)
      .on(
        'postgres_changes',
        {
          event,
          schema: 'public',
          table,
          ...(filter && { filter }),
        },
        callback
      )
      .subscribe()

    return () => {
      channel.unsubscribe()
    }
  }, [table, event, callback, filter])
}

8.2 错误处理 #

typescript
const channel = supabase
  .channel('error-handling')
  .on('postgres_changes', { ... }, (payload) => {
    if (payload.errors) {
      console.error('Subscription error:', payload.errors)
      return
    }
    // 处理数据
  })
  .subscribe((status, err) => {
    if (err) {
      console.error('Connection error:', err)
    }
  })

九、总结 #

数据库变更监听要点:

操作 说明
启用监听 ALTER PUBLICATION supabase_realtime ADD TABLE
INSERT event: ‘INSERT’
UPDATE event: ‘UPDATE’
DELETE event: ‘DELETE’
全部 event: ‘*’
过滤 filter: ‘column=eq.value’

下一步,让我们学习广播与状态!

最后更新:2026-03-28