@hsu-react/ui 2.5.3 → 2.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/components/ChainGraph/_hooks/useChainGraphData.js +29 -8
- package/es/components/ChainGraph/_hooks/useChainGraphLayout.js +14 -3
- package/es/components/Chart/Bar/index.js +49 -19
- package/es/components/Chart/Bubble/index.js +21 -6
- package/es/components/Chart/Common/index.js +7 -2
- package/es/components/Chart/Heatmap/index.js +21 -6
- package/es/components/Chart/Line/index.js +49 -19
- package/es/components/Chart/Pie/Pie3D/index.js +22 -6
- package/es/components/Chart/Pie/index.js +21 -6
- package/es/components/Chart/Sankey/index.js +11 -2
- package/es/components/Chart/Tree/index.js +11 -2
- package/es/components/Chat/ChatInput/index.js +7 -2
- package/es/components/Icon/index.js +8 -2
- package/es/components/Input/Number/index.js +28 -21
- package/es/components/Input/Password/index.js +44 -36
- package/es/components/Input/Search/index.js +44 -36
- package/es/components/Input/TextArea/index.js +64 -42
- package/es/components/Input/index.js +40 -34
- package/es/components/Select/_hooks/useSelectComposition.js +6 -2
- package/es/components/Select/_hooks/useSelectInputOptions.js +8 -2
- package/es/components/Slider/index.js +24 -15
- package/es/components/Table/_hooks/useAutoScrolling.js +16 -7
- package/es/components/Upload/_hooks/useUploadFileList.js +8 -2
- package/es/components/Upload/_hooks/useUploadOperations.js +8 -2
- package/es/hooks/useLatestRef.d.ts +24 -0
- package/es/hooks/useLatestRef.js +29 -0
- package/es/layout/Menu/_hooks/useOnlyLvOneMenu.js +9 -2
- package/lib/components/ChainGraph/_hooks/useChainGraphData.js +24 -7
- package/lib/components/ChainGraph/_hooks/useChainGraphLayout.js +12 -2
- package/lib/components/Chart/Bar/index.js +45 -18
- package/lib/components/Chart/Bubble/index.js +19 -6
- package/lib/components/Chart/Common/index.js +6 -2
- package/lib/components/Chart/Heatmap/index.js +19 -6
- package/lib/components/Chart/Line/index.js +45 -18
- package/lib/components/Chart/Pie/Pie3D/index.js +20 -6
- package/lib/components/Chart/Pie/index.js +19 -6
- package/lib/components/Chart/Sankey/index.js +10 -2
- package/lib/components/Chart/Tree/index.js +10 -2
- package/lib/components/Chat/ChatInput/index.js +6 -2
- package/lib/components/Icon/index.js +7 -2
- package/lib/components/Input/Number/index.js +27 -18
- package/lib/components/Input/Password/index.js +40 -26
- package/lib/components/Input/Search/index.js +42 -26
- package/lib/components/Input/TextArea/index.js +54 -26
- package/lib/components/Input/index.js +37 -24
- package/lib/components/Select/_hooks/useSelectComposition.js +6 -2
- package/lib/components/Select/_hooks/useSelectInputOptions.js +7 -2
- package/lib/components/Slider/index.js +23 -11
- package/lib/components/Table/_hooks/useAutoScrolling.js +16 -7
- package/lib/components/Upload/_hooks/useUploadFileList.js +7 -2
- package/lib/components/Upload/_hooks/useUploadOperations.js +7 -2
- package/lib/hooks/useLatestRef.d.ts +24 -0
- package/lib/hooks/useLatestRef.js +35 -0
- package/lib/layout/Menu/_hooks/useOnlyLvOneMenu.js +8 -2
- package/package.json +9 -3
- package/src/__tests__/callbackDepsGuard.test.ts +119 -0
- package/src/__tests__/setup.ts +26 -0
- package/src/components/ChainGraph/_hooks/callbacks.test.tsx +155 -0
- package/src/components/ChainGraph/_hooks/useChainGraphData.ts +38 -10
- package/src/components/ChainGraph/_hooks/useChainGraphLayout.ts +16 -3
- package/src/components/Chart/Bar/index.tsx +59 -21
- package/src/components/Chart/Bubble/index.tsx +22 -7
- package/src/components/Chart/Common/index.tsx +7 -2
- package/src/components/Chart/Heatmap/index.tsx +25 -6
- package/src/components/Chart/Line/index.tsx +59 -21
- package/src/components/Chart/Pie/Pie3D/index.tsx +22 -7
- package/src/components/Chart/Pie/index.tsx +22 -7
- package/src/components/Chart/Sankey/index.tsx +10 -4
- package/src/components/Chart/Tree/index.tsx +10 -4
- package/src/components/Chart/callbacks.test.tsx +315 -0
- package/src/components/Chat/ChatInput/index.tsx +7 -2
- package/src/components/Icon/index.tsx +7 -2
- package/src/components/Input/Number/index.tsx +25 -23
- package/src/components/Input/Password/index.tsx +37 -26
- package/src/components/Input/Search/index.tsx +39 -26
- package/src/components/Input/TextArea/index.test.tsx +208 -0
- package/src/components/Input/TextArea/index.tsx +56 -30
- package/src/components/Input/index.test.tsx +189 -0
- package/src/components/Input/index.tsx +36 -29
- package/src/components/Select/_hooks/useSelectComposition.test.ts +47 -0
- package/src/components/Select/_hooks/useSelectComposition.ts +6 -2
- package/src/components/Select/_hooks/useSelectInputOptions.ts +6 -9
- package/src/components/Slider/index.tsx +22 -12
- package/src/components/Table/_hooks/useAutoScrolling.test.tsx +95 -0
- package/src/components/Table/_hooks/useAutoScrolling.ts +16 -7
- package/src/components/Upload/_hooks/useUploadFileList.ts +6 -2
- package/src/components/Upload/_hooks/useUploadOperations.ts +7 -2
- package/src/hooks/useLatestRef.ts +31 -0
- package/src/layout/Menu/_hooks/useOnlyLvOneMenu.test.tsx +56 -0
- package/src/layout/Menu/_hooks/useOnlyLvOneMenu.ts +8 -2
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "../_utils/cartesian";
|
|
25
25
|
import { resolveChartChrome } from "../_utils/chartTheme";
|
|
26
26
|
import useIsDark from "../../../hooks/useIsDark";
|
|
27
|
+
import useLatestRef from "../../../hooks/useLatestRef";
|
|
27
28
|
|
|
28
29
|
export interface ChartLineProps extends ChartCommonProps {
|
|
29
30
|
chartTitle?: string;
|
|
@@ -90,6 +91,36 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
90
91
|
const isDark = useIsDark();
|
|
91
92
|
const chrome = useMemo(() => resolveChartChrome(isDark), [isDark]);
|
|
92
93
|
|
|
94
|
+
// 回调 prop 一律走 useLatestRef,不进依赖数组:消费方传内联箭头(React 里最常见的
|
|
95
|
+
// 写法)时每次渲染都是新引用,进依赖数组会让「init + setOption(notMerge)」整段重跑,
|
|
96
|
+
// 还会把当前可视窗口再同步一次给父级 —— 父级据此改 state 就是死循环。
|
|
97
|
+
// 但「有没有传回调」决定要不要注册监听,所以单独拆成布尔量进依赖数组,
|
|
98
|
+
// 这样「从无到有传入回调」仍会重新注册,不会漏。
|
|
99
|
+
const onClickRef = useLatestRef(onClick);
|
|
100
|
+
const onLegendSelectChangedRef = useLatestRef(onLegendSelectChanged);
|
|
101
|
+
const onDataZoomWindowChangedRef = useLatestRef(onDataZoomWindowChanged);
|
|
102
|
+
const hasOnClick = !!onClick;
|
|
103
|
+
const hasOnLegendSelectChanged = !!onLegendSelectChanged;
|
|
104
|
+
const hasOnDataZoomWindowChanged = !!onDataZoomWindowChanged;
|
|
105
|
+
// 已通知过父级的窗口用 ref 记账(不参与渲染):窗口没变就不再通知,
|
|
106
|
+
// 掐断「通知 → 父级重算坐标轴 → chartOption 变 → effect 重跑 → 再通知」的自激回路
|
|
107
|
+
const notifiedZoomWindowRef = useRef<DataZoomIndexWindow | null>(null);
|
|
108
|
+
const emitZoomWindow = useCallback(
|
|
109
|
+
(next: DataZoomIndexWindow) => {
|
|
110
|
+
const prev = notifiedZoomWindowRef.current;
|
|
111
|
+
if (
|
|
112
|
+
prev &&
|
|
113
|
+
prev.startIndex === next.startIndex &&
|
|
114
|
+
prev.endIndex === next.endIndex
|
|
115
|
+
) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
notifiedZoomWindowRef.current = next;
|
|
119
|
+
onDataZoomWindowChangedRef.current?.(next);
|
|
120
|
+
},
|
|
121
|
+
[onDataZoomWindowChangedRef],
|
|
122
|
+
);
|
|
123
|
+
|
|
93
124
|
// Cache the chart option with useMemo
|
|
94
125
|
const chartOption = useMemo(() => {
|
|
95
126
|
const def_xAxis = createDefaultCategoryXAxis(xAxisData, chrome);
|
|
@@ -326,18 +357,18 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
326
357
|
}
|
|
327
358
|
|
|
328
359
|
// Sync the current visible window to the caller (on initial render and after restore), used to recalculate axes based on the displayed portion
|
|
329
|
-
if (
|
|
360
|
+
if (hasOnDataZoomWindowChanged) {
|
|
330
361
|
const zooms = (chart.getOption() as ChartsOption | undefined)
|
|
331
362
|
?.dataZoom as Array<{ start?: number; end?: number }> | undefined;
|
|
332
363
|
const zoom = zooms?.[0];
|
|
333
364
|
const totalLen = xAxisData?.length ?? 0;
|
|
334
365
|
if (typeof zoom?.start === "number" && typeof zoom?.end === "number") {
|
|
335
|
-
|
|
366
|
+
emitZoomWindow(
|
|
336
367
|
percentWindowToIndexWindow(zoom.start, zoom.end, totalLen),
|
|
337
368
|
);
|
|
338
369
|
} else {
|
|
339
370
|
// Without dataZoom (e.g. scrolling not enabled due to insufficient data) the window is the full range; sync once to avoid a stale old window
|
|
340
|
-
|
|
371
|
+
emitZoomWindow({
|
|
341
372
|
startIndex: 0,
|
|
342
373
|
endIndex: Math.max(0, totalLen - 1),
|
|
343
374
|
});
|
|
@@ -353,18 +384,21 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
353
384
|
resizeObserverRef.current.observe(chartRef.current);
|
|
354
385
|
}
|
|
355
386
|
|
|
356
|
-
// Add click event
|
|
357
|
-
|
|
358
|
-
|
|
387
|
+
// Add click event(注册与否看布尔量,实际调用取最新引用)
|
|
388
|
+
const handleClick = (event: echarts.ECElementEvent) => {
|
|
389
|
+
onClickRef.current?.(event);
|
|
390
|
+
};
|
|
391
|
+
if (hasOnClick) {
|
|
392
|
+
chartInstanceRef.current?.on("click", handleClick);
|
|
359
393
|
}
|
|
360
394
|
|
|
361
395
|
// Legend selection event
|
|
362
396
|
const handleLegendSelectChanged = (params: unknown) => {
|
|
363
|
-
|
|
397
|
+
onLegendSelectChangedRef.current?.(
|
|
364
398
|
(params as { selected: Record<string, boolean> }).selected,
|
|
365
399
|
);
|
|
366
400
|
};
|
|
367
|
-
if (
|
|
401
|
+
if (hasOnLegendSelectChanged) {
|
|
368
402
|
chartInstanceRef.current?.on(
|
|
369
403
|
"legendselectchanged",
|
|
370
404
|
handleLegendSelectChanged,
|
|
@@ -390,7 +424,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
390
424
|
typeof info?.startValue === "number" &&
|
|
391
425
|
typeof info?.endValue === "number"
|
|
392
426
|
) {
|
|
393
|
-
|
|
427
|
+
emitZoomWindow({
|
|
394
428
|
startIndex: Math.max(0, Math.round(info.startValue)),
|
|
395
429
|
endIndex: Math.max(0, Math.round(info.endValue)),
|
|
396
430
|
});
|
|
@@ -398,7 +432,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
398
432
|
typeof info?.start === "number" &&
|
|
399
433
|
typeof info?.end === "number"
|
|
400
434
|
) {
|
|
401
|
-
|
|
435
|
+
emitZoomWindow(
|
|
402
436
|
percentWindowToIndexWindow(
|
|
403
437
|
info.start,
|
|
404
438
|
info.end,
|
|
@@ -407,7 +441,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
407
441
|
);
|
|
408
442
|
}
|
|
409
443
|
};
|
|
410
|
-
if (
|
|
444
|
+
if (hasOnDataZoomWindowChanged) {
|
|
411
445
|
chartInstanceRef.current?.on("datazoom", handleDataZoom);
|
|
412
446
|
}
|
|
413
447
|
|
|
@@ -443,18 +477,18 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
443
477
|
return () => {
|
|
444
478
|
window.removeEventListener("resize", handleResize);
|
|
445
479
|
|
|
446
|
-
if (
|
|
447
|
-
chartInstanceRef.current?.off("click",
|
|
480
|
+
if (hasOnClick) {
|
|
481
|
+
chartInstanceRef.current?.off("click", handleClick);
|
|
448
482
|
}
|
|
449
483
|
|
|
450
|
-
if (
|
|
484
|
+
if (hasOnLegendSelectChanged) {
|
|
451
485
|
chartInstanceRef.current?.off(
|
|
452
486
|
"legendselectchanged",
|
|
453
487
|
handleLegendSelectChanged,
|
|
454
488
|
);
|
|
455
489
|
}
|
|
456
490
|
|
|
457
|
-
if (
|
|
491
|
+
if (hasOnDataZoomWindowChanged) {
|
|
458
492
|
chartInstanceRef.current?.off("datazoom", handleDataZoom);
|
|
459
493
|
}
|
|
460
494
|
|
|
@@ -466,9 +500,12 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
466
500
|
}, [
|
|
467
501
|
chartOption,
|
|
468
502
|
handleResize,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
503
|
+
onClickRef,
|
|
504
|
+
onLegendSelectChangedRef,
|
|
505
|
+
hasOnClick,
|
|
506
|
+
hasOnLegendSelectChanged,
|
|
507
|
+
hasOnDataZoomWindowChanged,
|
|
508
|
+
emitZoomWindow,
|
|
472
509
|
xAxisData,
|
|
473
510
|
enableLegendAutoScroll,
|
|
474
511
|
legendVisibleCount,
|
|
@@ -505,9 +542,9 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
505
542
|
startIndex: normalizedScroll.startIndex,
|
|
506
543
|
autoPlay: normalizedScroll.autoScroll,
|
|
507
544
|
enableWheelScroll,
|
|
508
|
-
onWindowChange:
|
|
545
|
+
onWindowChange: hasOnDataZoomWindowChanged
|
|
509
546
|
? (startPercent, endPercent) =>
|
|
510
|
-
|
|
547
|
+
emitZoomWindow(
|
|
511
548
|
percentWindowToIndexWindow(
|
|
512
549
|
startPercent,
|
|
513
550
|
endPercent,
|
|
@@ -523,7 +560,8 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
523
560
|
normalizedScroll,
|
|
524
561
|
sliderDataZoom,
|
|
525
562
|
xAxisData,
|
|
526
|
-
|
|
563
|
+
hasOnDataZoomWindowChanged,
|
|
564
|
+
emitZoomWindow,
|
|
527
565
|
]);
|
|
528
566
|
|
|
529
567
|
// Clean up resources when the component unmounts
|
|
@@ -7,6 +7,7 @@ import { ChartCommonProps, ChartOptionType, ChartsOption } from "../..";
|
|
|
7
7
|
import { useMouseOverHandler, useGlobalOutHandler } from "./_hooks";
|
|
8
8
|
import { getPie3DOption } from "./_utils/option";
|
|
9
9
|
import styles from "../../index.module.scss";
|
|
10
|
+
import { useLatestRef } from "../../../../hooks/useLatestRef";
|
|
10
11
|
|
|
11
12
|
export interface Pie3DDataItem {
|
|
12
13
|
name: string;
|
|
@@ -170,6 +171,16 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
170
171
|
{ minHeight, maxHeight, yOffset, autoRotate }
|
|
171
172
|
);
|
|
172
173
|
|
|
174
|
+
const onChartRef = useLatestRef(onChart);
|
|
175
|
+
|
|
176
|
+
// onClick 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 跟着重跑会重建
|
|
177
|
+
// 3D 饼的 option 并重播动画。注册与否看布尔量(保证「从无到有传入」仍会注册),
|
|
178
|
+
// 实际调用取 ref 里的最新引用。
|
|
179
|
+
const onClickRef = useLatestRef(onClick);
|
|
180
|
+
const hasOnClick = !!onClick;
|
|
181
|
+
|
|
182
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
183
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
173
184
|
useEffect(() => {
|
|
174
185
|
if (!chartRef.current || !containerReady) return;
|
|
175
186
|
|
|
@@ -177,7 +188,7 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
177
188
|
if (!chart) {
|
|
178
189
|
chart = echarts.init(chartRef.current);
|
|
179
190
|
chartInstanceRef.current = chart;
|
|
180
|
-
|
|
191
|
+
onChartRef.current?.(chart);
|
|
181
192
|
}
|
|
182
193
|
|
|
183
194
|
chart.setOption(chartOption as ChartOptionType, true);
|
|
@@ -192,8 +203,11 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
192
203
|
|
|
193
204
|
chart.on("mouseover", handleMouseOver);
|
|
194
205
|
chart.on("globalout", handleGlobalOut);
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
const handleClick = (event: echarts.ECElementEvent) => {
|
|
207
|
+
onClickRef.current?.(event);
|
|
208
|
+
};
|
|
209
|
+
if (hasOnClick) {
|
|
210
|
+
chart.on("click", handleClick);
|
|
197
211
|
}
|
|
198
212
|
|
|
199
213
|
return () => {
|
|
@@ -201,8 +215,8 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
201
215
|
if (chart) {
|
|
202
216
|
chart.off("mouseover", handleMouseOver);
|
|
203
217
|
chart.off("globalout", handleGlobalOut);
|
|
204
|
-
if (
|
|
205
|
-
chart.off("click",
|
|
218
|
+
if (hasOnClick) {
|
|
219
|
+
chart.off("click", handleClick);
|
|
206
220
|
}
|
|
207
221
|
}
|
|
208
222
|
};
|
|
@@ -211,8 +225,9 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
211
225
|
handleResize,
|
|
212
226
|
handleMouseOver,
|
|
213
227
|
handleGlobalOut,
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
onChartRef,
|
|
229
|
+
onClickRef,
|
|
230
|
+
hasOnClick,
|
|
216
231
|
containerReady,
|
|
217
232
|
]);
|
|
218
233
|
|
|
@@ -13,6 +13,7 @@ import { autoScrollLegend } from "../chartUtils";
|
|
|
13
13
|
|
|
14
14
|
import styles from "../index.module.scss";
|
|
15
15
|
import ChartPie3D, { ChartPie3DProps } from "./Pie3D";
|
|
16
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
16
17
|
|
|
17
18
|
export interface ChartPieProps extends ChartCommonProps {
|
|
18
19
|
chartTitle?: string;
|
|
@@ -252,6 +253,16 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
252
253
|
chartInstanceRef.current?.resize();
|
|
253
254
|
}, []);
|
|
254
255
|
|
|
256
|
+
const onChartRef = useLatestRef(onChart);
|
|
257
|
+
|
|
258
|
+
// onClick 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 跟着重跑就会
|
|
259
|
+
// 再走一遍 setOption(notMerge),动画重播、悬浮/高亮态被清掉。注册与否看布尔量,
|
|
260
|
+
// 这样「从无到有传入 onClick」仍会注册;实际调用取 ref 里的最新引用。
|
|
261
|
+
const onClickRef = useLatestRef(onClick);
|
|
262
|
+
const hasOnClick = !!onClick;
|
|
263
|
+
|
|
264
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
265
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
255
266
|
// Initialize the chart
|
|
256
267
|
useEffect(() => {
|
|
257
268
|
if (!chartRef.current || !containerReady) return;
|
|
@@ -262,7 +273,7 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
262
273
|
chart = echarts.init(chartRef.current);
|
|
263
274
|
chartInstanceRef.current = chart;
|
|
264
275
|
// Call the onChart callback on first initialization
|
|
265
|
-
|
|
276
|
+
onChartRef.current?.(chart);
|
|
266
277
|
}
|
|
267
278
|
|
|
268
279
|
// Apply the option
|
|
@@ -278,8 +289,11 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
278
289
|
}
|
|
279
290
|
|
|
280
291
|
// Add click event
|
|
281
|
-
|
|
282
|
-
|
|
292
|
+
const handleClick = (event: echarts.ECElementEvent) => {
|
|
293
|
+
onClickRef.current?.(event);
|
|
294
|
+
};
|
|
295
|
+
if (hasOnClick) {
|
|
296
|
+
chartInstanceRef.current?.on("click", handleClick);
|
|
283
297
|
}
|
|
284
298
|
|
|
285
299
|
// Legend auto-scroll
|
|
@@ -302,8 +316,8 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
302
316
|
return () => {
|
|
303
317
|
window.removeEventListener("resize", handleResize);
|
|
304
318
|
|
|
305
|
-
if (
|
|
306
|
-
chartInstanceRef.current?.off("click",
|
|
319
|
+
if (hasOnClick) {
|
|
320
|
+
chartInstanceRef.current?.off("click", handleClick);
|
|
307
321
|
}
|
|
308
322
|
|
|
309
323
|
// Clean up legend scrolling
|
|
@@ -315,8 +329,9 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
315
329
|
}, [
|
|
316
330
|
chartOption,
|
|
317
331
|
handleResize,
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
onChartRef,
|
|
333
|
+
onClickRef,
|
|
334
|
+
hasOnClick,
|
|
320
335
|
enableLegendAutoScroll,
|
|
321
336
|
seriesData,
|
|
322
337
|
legendVisibleCount,
|
|
@@ -3,6 +3,7 @@ import useContainerReady from "../_hooks/useContainerReady";
|
|
|
3
3
|
import styles from "../index.module.scss";
|
|
4
4
|
import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
|
|
5
5
|
import * as echarts from "echarts";
|
|
6
|
+
import useLatestRef from "../../../hooks/useLatestRef";
|
|
6
7
|
|
|
7
8
|
type SankeyNodeItemOption = echarts.SankeySeriesOption["data"];
|
|
8
9
|
type SankeyEdgeItemOption = echarts.SankeySeriesOption["links"];
|
|
@@ -101,6 +102,13 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
101
102
|
return option;
|
|
102
103
|
}, [seriesData, seriesLinks, series]);
|
|
103
104
|
|
|
105
|
+
// getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
|
|
106
|
+
// 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
|
|
107
|
+
// → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
|
|
108
|
+
// 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
|
|
109
|
+
// 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
|
|
110
|
+
const getImageRef = useLatestRef(getImage);
|
|
111
|
+
|
|
104
112
|
// Callback handling chart resize
|
|
105
113
|
const handleResize = useCallback(() => {
|
|
106
114
|
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
@@ -123,7 +131,7 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
123
131
|
|
|
124
132
|
// Attach the finished event listener on first initialization
|
|
125
133
|
chart.on("finished", () => {
|
|
126
|
-
|
|
134
|
+
getImageRef.current?.(
|
|
127
135
|
chart!.getDataURL({
|
|
128
136
|
type: "png",
|
|
129
137
|
pixelRatio: 1,
|
|
@@ -149,9 +157,7 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
149
157
|
return () => {
|
|
150
158
|
window.removeEventListener("resize", handleResize);
|
|
151
159
|
};
|
|
152
|
-
}, [chartOption, handleResize,
|
|
153
|
-
containerReady,
|
|
154
|
-
]);
|
|
160
|
+
}, [chartOption, handleResize, getImageRef, containerReady]);
|
|
155
161
|
|
|
156
162
|
// Clean up resources on unmount
|
|
157
163
|
useEffect(() => {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SeriesData,
|
|
9
9
|
} from "..";
|
|
10
10
|
import * as echarts from "echarts";
|
|
11
|
+
import useLatestRef from "../../../hooks/useLatestRef";
|
|
11
12
|
import { deepCopy } from "hsu-utils";
|
|
12
13
|
import { handleTreeData } from "../_utils/tree";
|
|
13
14
|
|
|
@@ -41,6 +42,13 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
41
42
|
return option;
|
|
42
43
|
}, [seriesData, series]);
|
|
43
44
|
|
|
45
|
+
// getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
|
|
46
|
+
// 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
|
|
47
|
+
// → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
|
|
48
|
+
// 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
|
|
49
|
+
// 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
|
|
50
|
+
const getImageRef = useLatestRef(getImage);
|
|
51
|
+
|
|
44
52
|
// Callback handling chart resize
|
|
45
53
|
const handleResize = useCallback(() => {
|
|
46
54
|
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
@@ -63,7 +71,7 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
63
71
|
|
|
64
72
|
// Attach the finished event listener on first initialization
|
|
65
73
|
chart.on("finished", () => {
|
|
66
|
-
|
|
74
|
+
getImageRef.current?.(
|
|
67
75
|
chart!.getDataURL({
|
|
68
76
|
type: "png",
|
|
69
77
|
pixelRatio: 1,
|
|
@@ -89,9 +97,7 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
89
97
|
return () => {
|
|
90
98
|
window.removeEventListener("resize", handleResize);
|
|
91
99
|
};
|
|
92
|
-
}, [chartOption, handleResize,
|
|
93
|
-
containerReady,
|
|
94
|
-
]);
|
|
100
|
+
}, [chartOption, handleResize, getImageRef, containerReady]);
|
|
95
101
|
|
|
96
102
|
// Clean up resources on unmount
|
|
97
103
|
useEffect(() => {
|