perf: Socket 连接异步化,使用 requestIdleCallback 不阻塞首屏

- NotificationContext: 将 Socket 初始化包裹在 requestIdleCallback 中
- 设置 3 秒超时保护,确保连接不会被无限延迟
- 不支持 requestIdleCallback 的浏览器自动降级到 setTimeout(0)
- socket/index.js: 移除模块加载时的 console.log,减少首屏阻塞感知
- 所有页面的首屏渲染都不再被 Socket 连接阻塞

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-11-26 10:34:45 +08:00
parent 581e874b0d
commit 6886a649f5
2 changed files with 180 additions and 163 deletions

View File

@@ -649,9 +649,17 @@ export const NotificationProvider = ({ children }) => {
}, [adaptEventToNotification]); }, [adaptEventToNotification]);
// ========== 连接到 Socket 服务(⚡ 方案2: 只执行一次 ========== // ========== 连接到 Socket 服务(⚡ 异步初始化,不阻塞首屏 ==========
useEffect(() => { useEffect(() => {
logger.info('NotificationContext', '初始化 Socket 连接方案2只注册一次'); let cleanupCalled = false;
let idleCallbackId;
let timeoutId;
// ⚡ Socket 初始化函数(将在浏览器空闲时执行)
const initSocketConnection = () => {
if (cleanupCalled) return; // 防止组件卸载后执行
logger.info('NotificationContext', '初始化 Socket 连接(异步执行,不阻塞首屏)');
// ========== 监听连接成功(首次连接 + 重连) ========== // ========== 监听连接成功(首次连接 + 重连) ==========
socket.on('connect', () => { socket.on('connect', () => {
@@ -811,7 +819,7 @@ export const NotificationProvider = ({ children }) => {
} }
}); });
logger.info('NotificationContext', '所有监听器已注册(只注册一次)'); logger.info('NotificationContext', '所有监听器已注册');
// ========== 获取最大重连次数 ========== // ========== 获取最大重连次数 ==========
const maxAttempts = socket.getMaxReconnectAttempts?.() || Infinity; const maxAttempts = socket.getMaxReconnectAttempts?.() || Infinity;
@@ -821,11 +829,33 @@ export const NotificationProvider = ({ children }) => {
// ========== 启动连接 ========== // ========== 启动连接 ==========
logger.info('NotificationContext', '调用 socket.connect()'); logger.info('NotificationContext', '调用 socket.connect()');
socket.connect(); socket.connect();
};
// ⚡ 使用 requestIdleCallback 在浏览器空闲时初始化 Socket
// 降级到 setTimeout(0) 以兼容不支持的浏览器(如 Safari
if ('requestIdleCallback' in window) {
idleCallbackId = window.requestIdleCallback(initSocketConnection, {
timeout: 3000 // 最多等待 3 秒,确保连接不会延迟太久
});
logger.debug('NotificationContext', 'Socket 初始化已排入 requestIdleCallback');
} else {
timeoutId = setTimeout(initSocketConnection, 0);
logger.debug('NotificationContext', 'Socket 初始化已排入 setTimeout(0)(降级模式)');
}
// ========== 清理函数(组件卸载时) ========== // ========== 清理函数(组件卸载时) ==========
return () => { return () => {
cleanupCalled = true;
logger.info('NotificationContext', '清理 Socket 连接'); logger.info('NotificationContext', '清理 Socket 连接');
// 取消待执行的初始化
if (idleCallbackId && 'cancelIdleCallback' in window) {
window.cancelIdleCallback(idleCallbackId);
}
if (timeoutId) {
clearTimeout(timeoutId);
}
// 清理 reconnected 状态定时器 // 清理 reconnected 状态定时器
if (reconnectedTimerRef.current) { if (reconnectedTimerRef.current) {
clearTimeout(reconnectedTimerRef.current); clearTimeout(reconnectedTimerRef.current);

View File

@@ -10,25 +10,12 @@ import { socketService } from '../socketService';
export const socket = socketService; export const socket = socketService;
export { socketService }; export { socketService };
// ⚡ 新增:暴露 Socket 实例到 window用于调试和验证 // ⚡ 暴露 Socket 实例到 window用于调试和验证
// 注意:移除首屏加载时的日志,避免阻塞感知
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.socket = socketService; window.socket = socketService;
window.socketService = socketService; window.socketService = socketService;
// 日志已移除,如需调试可在控制台执行: console.log(window.socket)
console.log(
'%c[Socket Service] ✅ Socket instance exposed to window',
'color: #4CAF50; font-weight: bold; font-size: 14px;'
);
console.log(' 📍 window.socket:', window.socket);
console.log(' 📍 window.socketService:', window.socketService);
console.log(' 📍 Socket.IO instance:', window.socket?.socket);
console.log(' 📍 Connection status:', window.socket?.connected ? '✅ Connected' : '❌ Disconnected');
} }
// 打印当前使用的服务类型
console.log(
'%c[Socket Service] Using REAL Socket Service',
'color: #4CAF50; font-weight: bold; font-size: 12px;'
);
export default socket; export default socket;