From 24c8b3130114ea80a1be81b18279e8c1aa76e31a Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Fri, 28 Aug 2026 03:41:18 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=82=A1=E7=A5=A8=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5K=E7=BA=BF=20React=20310=20=E5=B4=A9=E6=BA=83(useMemo?= =?UTF-8?q?=E7=BD=AE=E4=BA=8Eearly=20return=E5=89=8D)=20+=20=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E8=8C=83=E5=9B=B4=E5=88=87=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 React error #310:displayData 的 useMemo 原置于 if(loading) early return 之后, 导致 loading 切换时 hook 数量变化触发崩溃。移到所有 early return 之前。 - K线一次性拉取 365 天,前端按 3月/6月/1年 切片展示,默认近3月。 --- pnpm-lock.yaml | 38 +++++++++++++---------- src/routes/stock.$code.tsx | 62 +++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c911b6..b714679 100755 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,7 +124,7 @@ importers: version: 11.18.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) input-otp: specifier: ^1.4.2 - version: 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) lucide-react: specifier: ^0.575.0 version: 0.575.0(react@19.2.7) @@ -148,7 +148,7 @@ importers: version: 2.15.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) sonner: specifier: ^2.0.7 - version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 2.0.8(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tailwind-merge: specifier: ^3.6.0 version: 3.6.0 @@ -1613,11 +1613,11 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventemitter3@4.0.1: + resolution: {integrity: sha512-MnI0l35oYL2C/c80rjJN7qu50MDx39yYE7y7oYck2YA3v+y7EaAenY8IU8AP4d1RWqE8VAKWFGSh3rfP87ll3g==} - fast-equals@5.4.0: - resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + fast-equals@5.4.1: + resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} engines: {node: '>=6.0.0'} fdir@6.5.0: @@ -1662,8 +1662,8 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - input-otp@1.4.2: - resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} + input-otp@1.5.0: + resolution: {integrity: sha512-3AcfdW1sNG0FmSA5hHMBXG7jNW5CdcdKs8ln8JOmn6S2SRkoXoJx+2UwC+ZvjflfnOdHJx0SfQ9/YjlxVYLtGQ==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc @@ -1944,11 +1944,15 @@ packages: resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} engines: {node: '>=10'} - sonner@2.0.7: - resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} + sonner@2.0.8: + resolution: {integrity: sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg==} peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -3492,9 +3496,9 @@ snapshots: escalade@3.2.0: {} - eventemitter3@4.0.7: {} + eventemitter3@4.0.1: {} - fast-equals@5.4.0: {} + fast-equals@5.4.1: {} fdir@6.5.0(picomatch@4.0.5): optionalDependencies: @@ -3520,7 +3524,7 @@ snapshots: graceful-fs@4.2.11: {} - input-otp@1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + input-otp@1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -3687,7 +3691,7 @@ snapshots: react-smooth@4.0.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - fast-equals: 5.4.0 + fast-equals: 5.4.1 prop-types: 15.8.1 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -3721,7 +3725,7 @@ snapshots: recharts@2.15.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: clsx: 2.1.1 - eventemitter3: 4.0.7 + eventemitter3: 4.0.1 lodash: 4.18.1 react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -3772,10 +3776,12 @@ snapshots: seroval@1.5.4: {} - sonner@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + sonner@2.0.8(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 source-map-js@1.2.1: {} diff --git a/src/routes/stock.$code.tsx b/src/routes/stock.$code.tsx index 73e361f..7a72c88 100755 --- a/src/routes/stock.$code.tsx +++ b/src/routes/stock.$code.tsx @@ -86,6 +86,7 @@ function StockDetail() { const [fundFlowLoading, setFundFlowLoading] = useState(false); const [fundFlowPeriod, setFundFlowPeriod] = useState(21); const [dailyTableDays, setDailyTableDays] = useState(7); + const [chartRange, setChartRange] = useState(90); // 默认近3月 const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [inCollection, setInCollection] = useState(false); @@ -175,9 +176,8 @@ function StockDetail() { // 阶段2:并行加载历史K线 + 资金流向(后置加载,不阻塞首屏) const addedDate = new Date(addedAt); - const now = new Date(); - const daysSinceAdded = Math.floor((now.getTime() - addedDate.getTime()) / (1000 * 60 * 60 * 24)); - const chartDays = Math.min(Math.max(90, daysSinceAdded), 120); + // 一次性拉取最多 1 年(365天)K线,展示范围由 chartRange 前端切片 + const chartDays = 365; setChartLoading(true); setFundFlowLoading(true); @@ -288,6 +288,14 @@ function StockDetail() { const roe = latest.indicators["加权净资产收益率"]; return typeof roe === "number" ? roe : null; }, [financialData]); + + // 展示数据 = 按 chartRange 切片最近 N 天(chartData 存了全年,这里只取窗口) + // 必须在所有 early return 之前调用,保证 hook 顺序一致 + const displayData = useMemo(() => { + if (chartData.length <= chartRange) return chartData; + return chartData.slice(chartData.length - chartRange); + }, [chartData, chartRange]); + if (loading) { return (
@@ -334,19 +342,19 @@ function StockDetail() { // 找到添加日期在图表中的位置 const addedDateObj = stockInfo ? new Date(stockInfo.addedAt) : null; const addedDateStr = addedDateObj ? addedDateObj.toLocaleDateString("zh-CN", { month: "2-digit", day: "2-digit" }) : ""; - const addedDataPoint = inCollection ? chartData.find(d => d.isAddedDate) : undefined; + const addedDataPoint = inCollection ? displayData.find(d => d.isAddedDate) : undefined; // 计算X轴刻度:保证添加日期始终有标签 const tickDates = (() => { - if (chartData.length === 0) return []; - const first = chartData[0].date; - const last = chartData[chartData.length - 1].date; + if (displayData.length === 0) return []; + const first = displayData[0].date; + const last = displayData[displayData.length - 1].date; const ticks = [first]; const added = addedDataPoint?.date; - const targetCount = Math.min(7, chartData.length); - const step = Math.max(1, Math.floor((chartData.length - 1) / (targetCount - 2))); - for (let i = step; i < chartData.length - 1; i += step) { - ticks.push(chartData[i].date); + const targetCount = Math.min(7, displayData.length); + const step = Math.max(1, Math.floor((displayData.length - 1) / (targetCount - 2))); + for (let i = step; i < displayData.length - 1; i += step) { + ticks.push(displayData[i].date); } if (added && !ticks.includes(added)) { ticks.push(added); @@ -516,12 +524,32 @@ function StockDetail() { K线图 - {chartLoading ? "(加载中...)" : `(${chartData.length}个交易日)`} + {chartLoading ? "(加载中...)" : `(${displayData.length}个交易日)`} - - 双指缩放 · 拖动选区查看 - +
+ {/* 时间范围:3月 / 6月 / 1年 */} +
+ {[ + { key: 90, label: "3月" }, + { key: 180, label: "6月" }, + { key: 365, label: "1年" }, + ].map((r) => ( + + ))} +
+ + 双指缩放 · 拖动选区查看 + +
@@ -540,7 +568,7 @@ function StockDetail() { > @@ -554,7 +582,7 @@ function StockDetail() { height={56} tick={(props: any) => { const { x, y, payload } = props; - const dataPoint = chartData.find(d => d.date === payload.value); + const dataPoint = displayData.find(d => d.date === payload.value); const isAddedDate = inCollection && dataPoint?.isAddedDate; return (