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