DOM 操作优化与缓存管理

性能优化 - React.memo、API并行化、useReducer重构
This commit is contained in:
zdl
2026-01-15 17:50:05 +08:00
parent 6cf9dca324
commit afdc94049c
4 changed files with 74 additions and 38 deletions

View File

@@ -18,15 +18,10 @@ import { Box, Text, VStack, Tooltip } from "@chakra-ui/react";
import { keyframes } from "@emotion/react";
import dayjs from "dayjs";
// 动画定义
// 动画定义 - 使用 transform 代替 background-positionGPU 加速)
const shimmer = keyframes`
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
`;
const glow = keyframes`
0%, 100% { box-shadow: 0 0 5px rgba(212, 175, 55, 0.3); }
50% { box-shadow: 0 0 20px rgba(212, 175, 55, 0.6); }
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
`;
/**
@@ -408,12 +403,9 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
const dateStr = dayjs(date).format("YYYYMMDD");
const dateData = dataMapRef.current.get(dateStr);
// 找到 day-top 容器并插入自定义内容
// 找到 day-top 容器并插入自定义内容(直接替换,无需先清空)
const dayTop = el.querySelector(".fc-daygrid-day-top");
if (dayTop) {
// 清空默认内容
dayTop.innerHTML = "";
// 插入自定义内容
dayTop.innerHTML = createCellContentHTML(date, dateData, isToday);
}
}, []);
@@ -463,17 +455,17 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
overflow="hidden"
position="relative"
>
{/* 闪光效果 */}
{/* 闪光效果 - 使用 transform 实现 GPU 加速 */}
<Box
position="absolute"
top="0"
left="0"
right="0"
bottom="0"
bgGradient="linear(to-r, transparent, rgba(255,255,255,0.3), transparent)"
backgroundSize="200% 100%"
animation={`${shimmer} 3s linear infinite`}
opacity={0.5}
w="100%"
h="100%"
bgGradient="linear(to-r, transparent, rgba(255,255,255,0.4), transparent)"
animation={`${shimmer} 2.5s ease-in-out infinite`}
opacity={0.6}
pointerEvents="none"
/>
<Text
fontSize="xs"
@@ -579,7 +571,8 @@ export const FullCalendarPro: React.FC<FullCalendarProProps> = ({
},
".fc-daygrid-day.fc-day-today": {
bg: "rgba(212, 175, 55, 0.15) !important",
animation: `${glow} 2s ease-in-out infinite`,
boxShadow: "0 0 15px rgba(212, 175, 55, 0.5)",
// 移除呼吸动画,使用固定 boxShadow 高亮"今天",避免内容闪烁
},
".fc-daygrid-day-frame": {
minHeight: "50px",

View File

@@ -1,5 +1,5 @@
// HeroPanel - 综合日历组件
import React, { useState, useEffect, useCallback, Suspense, lazy, memo } from "react";
import React, { useState, useEffect, useCallback, Suspense, lazy, memo, useRef } from "react";
import {
Box,
HStack,
@@ -43,12 +43,23 @@ const CombinedCalendar = memo(({ DetailModal }) => {
const [detailLoading, setDetailLoading] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
// 加载日历综合数据(一次 API 调用获取所有数据
// 月份数据缓存(避免切换月份后再切回时重复请求
const monthCacheRef = useRef({});
// 加载日历综合数据(带缓存)
useEffect(() => {
const loadCalendarCombinedData = async () => {
try {
const year = currentMonth.getFullYear();
const month = currentMonth.getMonth() + 1;
const cacheKey = `${year}-${month}`;
// 检查缓存
if (monthCacheRef.current[cacheKey]) {
setCalendarData(monthCacheRef.current[cacheKey]);
return;
}
try {
const response = await fetch(
`${getApiBase()}/api/v1/calendar/combined-data?year=${year}&month=${month}`
);
@@ -63,10 +74,8 @@ const CombinedCalendar = memo(({ DetailModal }) => {
eventCount: item.event_count || 0,
indexChange: item.index_change,
}));
console.log(
"[HeroPanel] 加载日历综合数据成功,数据条数:",
formattedData.length
);
// 存入缓存
monthCacheRef.current[cacheKey] = formattedData;
setCalendarData(formattedData);
}
}

View File

@@ -268,14 +268,23 @@ const DetailModal = ({
[dispatch, isStockInWatchlist]
);
// 加载股票行情(并行加载优化
// 加载股票行情(并行加载 + 缓存去重
const loadStockQuotes = useCallback(
async (stocks) => {
if (!stocks || stocks.length === 0) return;
// 过滤已缓存的股票,只请求未缓存的
const uncachedStocks = stocks.filter(
(stock) => !stockQuotes[stock.code]
);
// 如果全部已缓存,无需请求
if (uncachedStocks.length === 0) return;
setStockQuotesLoading(true);
// 并行发起所有请求
const promises = stocks.map(async (stock) => {
// 并行发起未缓存股票的请求
const promises = uncachedStocks.map(async (stock) => {
const code = getSixDigitCode(stock.code);
try {
const response = await fetch(
@@ -304,18 +313,18 @@ const DetailModal = ({
// 等待所有请求完成
const results = await Promise.all(promises);
// 构建 quotes 对象
const quotes = {};
// 合并新数据到现有缓存
const newQuotes = { ...stockQuotes };
results.forEach((result) => {
if (result) {
quotes[result.stockCode] = result.quote;
newQuotes[result.stockCode] = result.quote;
}
});
setStockQuotes(quotes);
setStockQuotes(newQuotes);
setStockQuotesLoading(false);
},
[setStockQuotes, setStockQuotesLoading]
[stockQuotes, setStockQuotes, setStockQuotesLoading]
);
// 显示相关股票

View File

@@ -4,7 +4,7 @@
* Y轴板块热度涨停家数
* 支持时间滑动条查看历史数据
*/
import React, { useState, useEffect, useMemo, useCallback } from "react";
import React, { useState, useEffect, useMemo, useCallback, useRef } from "react";
import {
Box,
Text,
@@ -38,6 +38,9 @@ import {
/**
* ThemeCometChart 主组件
*/
// 缓存有效期5 分钟)
const CACHE_DURATION = 5 * 60 * 1000;
const ThemeCometChart = ({ onThemeSelect }) => {
const [loading, setLoading] = useState(true);
const [allDatesData, setAllDatesData] = useState({});
@@ -48,8 +51,24 @@ const ThemeCometChart = ({ onThemeSelect }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const toast = useToast();
// 加载所有日期的数据
// 数据缓存(避免 tab 切换时重复请求)
const dataCacheRef = useRef({ data: null, dates: null, timestamp: null });
// 加载所有日期的数据(带缓存)
const loadAllData = useCallback(async () => {
// 检查缓存是否有效5分钟内
const now = Date.now();
if (
dataCacheRef.current.timestamp &&
now - dataCacheRef.current.timestamp < CACHE_DURATION &&
dataCacheRef.current.data
) {
setAllDatesData(dataCacheRef.current.data);
setAvailableDates(dataCacheRef.current.dates);
setLoading(false);
return;
}
setLoading(true);
try {
const apiBase = getApiBase();
@@ -109,6 +128,12 @@ const ThemeCometChart = ({ onThemeSelect }) => {
}
});
// 存入缓存
dataCacheRef.current = {
data: dataCache,
dates: dates,
timestamp: Date.now(),
};
setAllDatesData(dataCache);
setSliderIndex(0);
} else {