Feat:增加SSE新帖推送
This commit is contained in:
100
src/hooks/useSSE.ts
Normal file
100
src/hooks/useSSE.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { API_CONFIG } from '../config';
|
||||
|
||||
interface UseSSEOptions {
|
||||
onNewPost?: () => void;
|
||||
maxRetries?: number;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export function useSSE(options: UseSSEOptions = {}) {
|
||||
const {
|
||||
onNewPost,
|
||||
maxRetries = 3,
|
||||
enabled = true,
|
||||
} = options;
|
||||
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const eventSourceRef = useRef<EventSource | null>(null);
|
||||
const retriesRef = useRef(0);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const onNewPostRef = useRef(onNewPost);
|
||||
|
||||
// 更新 ref 以获取最新的回调
|
||||
useEffect(() => {
|
||||
onNewPostRef.current = onNewPost;
|
||||
}, [onNewPost]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connect = () => {
|
||||
// 如果已经达到最大重连次数,放弃连接
|
||||
if (retriesRef.current >= maxRetries) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const eventSource = new EventSource(`${API_CONFIG.BASE_URL}/stream`);
|
||||
eventSourceRef.current = eventSource;
|
||||
|
||||
eventSource.onopen = () => {
|
||||
setIsConnected(true);
|
||||
retriesRef.current = 0; // 重置重试计数
|
||||
};
|
||||
|
||||
eventSource.onmessage = (event) => {
|
||||
const data = event.data;
|
||||
|
||||
if (data === 'heartbeat') {
|
||||
// 心跳消息,不做处理
|
||||
return;
|
||||
}
|
||||
|
||||
if (data === 'new_post') {
|
||||
// 新投稿通知
|
||||
onNewPostRef.current?.();
|
||||
}
|
||||
};
|
||||
|
||||
eventSource.onerror = () => {
|
||||
setIsConnected(false);
|
||||
eventSource.close();
|
||||
|
||||
// 如果还没达到最大重连次数,尝试重连
|
||||
if (retriesRef.current < maxRetries) {
|
||||
retriesRef.current += 1;
|
||||
|
||||
// 使用指数退避策略重连
|
||||
const delay = Math.min(1000 * Math.pow(2, retriesRef.current - 1), 10000);
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
connect();
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
} catch {
|
||||
setIsConnected(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始连接
|
||||
connect();
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
if (eventSourceRef.current) {
|
||||
eventSourceRef.current.close();
|
||||
eventSourceRef.current = null;
|
||||
}
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
reconnectTimeoutRef.current = null;
|
||||
}
|
||||
setIsConnected(false);
|
||||
};
|
||||
}, [enabled, maxRetries]);
|
||||
|
||||
return { isConnected };
|
||||
}
|
||||
Reference in New Issue
Block a user