import { useEffect, useRef } from 'react' import { useRealtime } from './context' import type { KenEventEnvelope } from './kenEvent' export interface ResourceEventsSpec { topic: string tags: readonly string[] filter?: Record /** 资源 id 缺失时不要订阅(例如详情尚未加载)。 */ enabled?: boolean /** 事件到达后执行;默认按 150ms 合并同批事件。 */ onEvent: (envelope: KenEventEnvelope) => void debounceMs?: number } /** * 订阅某个资源的若干 tag,事件到达后回调。引用计数与重放由 realtime 层负责, * 组件卸载即退订。回调只在合并窗口内触发一次,避免一次批量写库打爆请求。 */ export function useResourceEvents({ topic, tags, filter, enabled = true, onEvent, debounceMs = 150, }: ResourceEventsSpec): void { const realtime = useRealtime() const handler = useRef(onEvent) useEffect(() => { handler.current = onEvent }) const signature = `${topic}|${tags.join(',')}|${JSON.stringify(filter ?? {})}` const latest = useRef({ tags, filter }) latest.current = { tags, filter } useEffect(() => { if (!enabled || !realtime.enabled) return const { tags, filter } = latest.current let timer: number | undefined let queued: KenEventEnvelope | null = null const flush = () => { timer = undefined if (!queued) return const envelope = queued queued = null handler.current(envelope) } const releases = tags.map((tag) => realtime.subscribe({ topic, tag, filter, key: `${topic}:${tag}:${signature}` })) const off = realtime.onEnvelope((envelope) => { if (envelope.topic !== topic) return if (!tags.includes(envelope.tag)) return queued = envelope if (timer === undefined) timer = window.setTimeout(flush, debounceMs) }) return () => { off() for (const release of releases) release() if (timer !== undefined) window.clearTimeout(timer) } }, [realtime, signature, enabled, debounceMs, topic]) }