diff --git a/src/App.js b/src/App.js index 5667aa3e..88f8b773 100755 --- a/src/App.js +++ b/src/App.js @@ -27,19 +27,66 @@ import { PerformancePanel } from './components/PerformancePanel'; import { useGlobalErrorHandler } from './hooks/useGlobalErrorHandler'; // Redux -import { initializePostHog } from './store/slices/posthogSlice'; +// ⚡ PostHog 延迟加载:移除同步导入,首屏减少 ~180KB +// import { initializePostHog } from './store/slices/posthogSlice'; import { updateScreenSize } from './store/slices/deviceSlice'; +import { injectReducer } from './store'; // Utils import { logger } from './utils/logger'; import { performanceMonitor } from './utils/performanceMonitor'; -// PostHog 追踪 -import { trackEvent, trackEventAsync } from '@lib/posthog'; +// ⚡ PostHog 延迟加载:移除同步导入 +// import { trackEvent, trackEventAsync } from '@lib/posthog'; // Contexts import { useAuth } from '@contexts/AuthContext'; +// ⚡ PostHog 延迟加载模块(动态导入后缓存) +let posthogModule = null; +let posthogSliceModule = null; + +/** + * ⚡ 延迟加载 PostHog 模块 + * 返回 { trackEvent, trackEventAsync, initializePostHog, posthogReducer } + */ +const loadPostHogModules = async () => { + if (posthogModule && posthogSliceModule) { + return { posthogModule, posthogSliceModule }; + } + + try { + const [posthog, posthogSlice] = await Promise.all([ + import('@lib/posthog'), + import('./store/slices/posthogSlice'), + ]); + + posthogModule = posthog; + posthogSliceModule = posthogSlice; + + return { posthogModule, posthogSliceModule }; + } catch (error) { + logger.error('App', 'PostHog 模块加载失败', error); + return null; + } +}; + +/** + * ⚡ 异步追踪事件(延迟加载 PostHog 后调用) + * @param {string} eventName - 事件名称 + * @param {object} properties - 事件属性 + */ +const trackEventLazy = async (eventName, properties = {}) => { + // 等待模块加载完成 + if (!posthogModule) { + const modules = await loadPostHogModules(); + if (!modules) return; + } + + // 使用异步追踪,不阻塞主线程 + posthogModule.trackEventAsync(eventName, properties); +}; + /** * AppContent - 应用核心内容 * 负责 PostHog 初始化和渲染路由 @@ -53,12 +100,25 @@ function AppContent() { const pageEnterTimeRef = useRef(Date.now()); const currentPathRef = useRef(location.pathname); - // 🎯 PostHog Redux 初始化(延迟执行,不阻塞首屏) + // 🎯 ⚡ PostHog 延迟加载 + Redux 初始化(首屏不加载 ~180KB) useEffect(() => { - // ⚡ 延迟 2 秒初始化 PostHog,确保首屏渲染不被阻塞 - const timer = setTimeout(() => { - dispatch(initializePostHog()); - logger.info('App', 'PostHog Redux 初始化已触发(延迟 2 秒)'); + // ⚡ 延迟 2 秒加载 PostHog 模块,确保首屏渲染不被阻塞 + const timer = setTimeout(async () => { + try { + const modules = await loadPostHogModules(); + if (!modules) return; + + const { posthogSliceModule } = modules; + + // 动态注入 PostHog reducer + injectReducer('posthog', posthogSliceModule.default); + + // 初始化 PostHog + dispatch(posthogSliceModule.initializePostHog()); + logger.info('App', 'PostHog 模块延迟加载完成,Redux 初始化已触发'); + } catch (error) { + logger.error('App', 'PostHog 延迟加载失败', error); + } }, 2000); return () => clearTimeout(timer); @@ -97,8 +157,8 @@ function AppContent() { if (!hasVisited) { const urlParams = new URLSearchParams(location.search); - // ⚡ 使用异步追踪,不阻塞页面渲染 - trackEventAsync('first_visit', { + // ⚡ 使用延迟加载的异步追踪,不阻塞页面渲染 + trackEventLazy('first_visit', { referrer: document.referrer || 'direct', utm_source: urlParams.get('utm_source'), utm_medium: urlParams.get('utm_medium'), @@ -120,8 +180,8 @@ function AppContent() { // 只追踪停留时间 > 1 秒的页面(过滤快速跳转) if (duration > 1) { - // ⚡ 使用异步追踪,不阻塞页面切换 - trackEventAsync('page_view_duration', { + // ⚡ 使用延迟加载的异步追踪,不阻塞页面切换 + trackEventLazy('page_view_duration', { path: currentPathRef.current, duration_seconds: duration, is_authenticated: isAuthenticated, diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js index 50633641..6ed80ced 100755 --- a/src/contexts/AuthContext.js +++ b/src/contexts/AuthContext.js @@ -5,9 +5,58 @@ import { useToast } from '@chakra-ui/react'; import { logger } from '@utils/logger'; import { performanceMonitor } from '@utils/performanceMonitor'; import { useNotification } from '@contexts/NotificationContext'; -import { identifyUser, resetUser, trackEvent } from '@lib/posthog'; +// ⚡ PostHog 延迟加载:移除同步导入,首屏减少 ~180KB +// import { identifyUser, resetUser, trackEvent } from '@lib/posthog'; import { SPECIAL_EVENTS } from '@lib/constants'; +// ⚡ PostHog 延迟加载模块(动态导入后缓存) +let posthogModule = null; + +/** + * ⚡ 延迟加载 PostHog 模块 + */ +const loadPostHogModule = async () => { + if (posthogModule) return posthogModule; + + try { + posthogModule = await import('@lib/posthog'); + return posthogModule; + } catch (error) { + logger.error('AuthContext', 'PostHog 模块加载失败', error); + return null; + } +}; + +/** + * ⚡ 延迟调用 identifyUser + */ +const identifyUserLazy = async (userId, userProperties) => { + const module = await loadPostHogModule(); + if (module) { + module.identifyUser(userId, userProperties); + } +}; + +/** + * ⚡ 延迟调用 resetUser + */ +const resetUserLazy = async () => { + const module = await loadPostHogModule(); + if (module) { + module.resetUser(); + } +}; + +/** + * ⚡ 延迟调用 trackEvent(使用异步版本) + */ +const trackEventLazy = async (eventName, properties) => { + const module = await loadPostHogModule(); + if (module) { + module.trackEventAsync(eventName, properties); + } +}; + // 创建认证上下文 const AuthContext = createContext(); @@ -99,8 +148,8 @@ export const AuthProvider = ({ children }) => { return prevUser; } - // ✅ 识别用户身份到 PostHog - identifyUser(data.user.id, { + // ✅ 识别用户身份到 PostHog(延迟加载) + identifyUserLazy(data.user.id, { email: data.user.email, username: data.user.username, subscription_tier: data.user.subscription_tier, @@ -354,8 +403,8 @@ export const AuthProvider = ({ children }) => { credentials: 'include' }); - // ✅ 追踪登出事件(必须在 resetUser() 之前,否则会丢失用户身份) - trackEvent(SPECIAL_EVENTS.USER_LOGGED_OUT, { + // ✅ 追踪登出事件(延迟加载,必须在 resetUser() 之前) + trackEventLazy(SPECIAL_EVENTS.USER_LOGGED_OUT, { timestamp: new Date().toISOString(), user_id: user?.id || null, session_duration_minutes: user?.session_start @@ -363,8 +412,8 @@ export const AuthProvider = ({ children }) => { : null, }); - // ✅ 重置 PostHog 用户会话 - resetUser(); + // ✅ 重置 PostHog 用户会话(延迟加载) + resetUserLazy(); // 清除本地状态 setUser(null); diff --git a/src/index.js b/src/index.js index f6c49045..84dd3555 100755 --- a/src/index.js +++ b/src/index.js @@ -7,8 +7,8 @@ import { BrowserRouter as Router } from 'react-router-dom'; import { performanceMonitor } from './utils/performanceMonitor'; performanceMonitor.mark('app-start'); -// 导入 Brainwave 样式(空文件,保留以避免错误) -import './styles/brainwave.css'; +// ⚡ 已删除 brainwave.css(项目未安装 Tailwind CSS,该文件无效) +// import './styles/brainwave.css'; // 导入 Select 下拉框颜色修复样式 import './styles/select-fix.css'; diff --git a/src/store/index.js b/src/store/index.js index cbdaebb6..191439a2 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,26 +1,38 @@ // src/store/index.js -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, combineReducers } from '@reduxjs/toolkit'; import communityDataReducer from './slices/communityDataSlice'; -import posthogReducer from './slices/posthogSlice'; +// ⚡ PostHog 延迟加载:移除同步导入,首屏减少 ~180KB +// import posthogReducer from './slices/posthogSlice'; +// import posthogMiddleware from './middleware/posthogMiddleware'; import industryReducer from './slices/industrySlice'; import stockReducer from './slices/stockSlice'; import authModalReducer from './slices/authModalSlice'; import subscriptionReducer from './slices/subscriptionSlice'; import deviceReducer from './slices/deviceSlice'; // ✅ 设备检测状态管理 -import posthogMiddleware from './middleware/posthogMiddleware'; import { eventsApi } from './api/eventsApi'; // ✅ RTK Query API +// ⚡ 基础 reducers(首屏必需) +const staticReducers = { + communityData: communityDataReducer, + industry: industryReducer, // ✅ 行业分类数据管理 + stock: stockReducer, // ✅ 股票和事件数据管理 + authModal: authModalReducer, // ✅ 认证弹窗状态管理 + subscription: subscriptionReducer, // ✅ 订阅信息状态管理 + device: deviceReducer, // ✅ 设备检测状态管理(移动端/桌面端) + [eventsApi.reducerPath]: eventsApi.reducer, // ✅ RTK Query 事件 API +}; + +// ⚡ 动态 reducers 注册表 +const asyncReducers = {}; + +// ⚡ 创建根 reducer 的工厂函数 +const createRootReducer = () => combineReducers({ + ...staticReducers, + ...asyncReducers, +}); + export const store = configureStore({ - reducer: { - communityData: communityDataReducer, - posthog: posthogReducer, // ✅ PostHog Redux 状态管理 - industry: industryReducer, // ✅ 行业分类数据管理 - stock: stockReducer, // ✅ 股票和事件数据管理 - authModal: authModalReducer, // ✅ 认证弹窗状态管理 - subscription: subscriptionReducer, // ✅ 订阅信息状态管理 - device: deviceReducer, // ✅ 设备检测状态管理(移动端/桌面端) - [eventsApi.reducerPath]: eventsApi.reducer, // ✅ RTK Query 事件 API - }, + reducer: createRootReducer(), middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { @@ -28,14 +40,27 @@ export const store = configureStore({ ignoredActions: [ 'communityData/fetchPopularKeywords/fulfilled', 'communityData/fetchHotEvents/fulfilled', - 'posthog/trackEvent/fulfilled', // ✅ PostHog 事件追踪 + 'posthog/trackEvent/fulfilled', // ✅ PostHog 事件追踪(延迟加载后仍需) 'stock/fetchEventStocks/fulfilled', 'stock/fetchStockQuotes/fulfilled', ], }, }) - .concat(posthogMiddleware) // ✅ PostHog 自动追踪中间件 + // ⚡ PostHog 中间件延迟加载,首屏不再需要 .concat(eventsApi.middleware), // ✅ RTK Query 中间件(自动缓存、去重、重试) }); +/** + * ⚡ 动态注入 reducer(用于延迟加载模块) + * @param {string} key - reducer 的键名 + * @param {Function} reducer - reducer 函数 + */ +export const injectReducer = (key, reducer) => { + if (asyncReducers[key]) { + return; // 已注入,避免重复 + } + asyncReducers[key] = reducer; + store.replaceReducer(createRootReducer()); +}; + export default store; diff --git a/src/styles/brainwave.css b/src/styles/brainwave.css deleted file mode 100644 index 4821c5d2..00000000 --- a/src/styles/brainwave.css +++ /dev/null @@ -1,12 +0,0 @@ -/* Tailwind CSS 入口文件 */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -/* 自定义工具类 */ -@layer utilities { - /* 毛玻璃效果 */ - .backdrop-blur-xl { - backdrop-filter: blur(24px); - } -} diff --git a/src/views/Community/index.js b/src/views/Community/index.js index 72f92d6d..f7e01818 100644 --- a/src/views/Community/index.js +++ b/src/views/Community/index.js @@ -1,5 +1,5 @@ // src/views/Community/index.js -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, lazy, Suspense } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; import { @@ -20,12 +20,14 @@ import { VStack, Text, useBreakpointValue, + Skeleton, } from '@chakra-ui/react'; // 导入组件 import DynamicNewsCard from './components/DynamicNewsCard'; import HotEventsSection from './components/HotEventsSection'; -import HeroPanel from './components/HeroPanel'; +// ⚡ HeroPanel 懒加载:包含 ECharts (~600KB),首屏不需要立即渲染 +const HeroPanel = lazy(() => import('./components/HeroPanel')); // 导入自定义 Hooks import { useEventData } from './hooks/useEventData'; @@ -272,8 +274,14 @@ const Community = () => { )} - {/* 顶部说明面板:产品介绍 + 沪深指数 + 热门概念词云 */} - + {/* ⚡ 顶部说明面板(懒加载):产品介绍 + 沪深指数 + 热门概念词云 */} + + + + }> + + {/* 实时要闻·动态追踪 - 横向滚动 */}