@hsu-react/ui 2.5.4 → 2.5.6
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 +55 -20
- package/es/components/Chart/Bubble/index.js +21 -6
- package/es/components/Chart/Common/index.js +6 -1
- package/es/components/Chart/Gauge/index.js +6 -1
- package/es/components/Chart/Heatmap/index.js +24 -7
- package/es/components/Chart/Line/index.js +55 -20
- package/es/components/Chart/Pie/Pie3D/index.js +30 -12
- package/es/components/Chart/Pie/index.js +24 -7
- package/es/components/Chart/Polar/index.js +9 -2
- package/es/components/Chart/Radar/index.js +6 -1
- package/es/components/Chart/Sankey/index.js +11 -2
- package/es/components/Chart/Tree/index.js +11 -2
- package/es/components/Select/_hooks/useSelectComposition.js +6 -2
- package/es/components/Table/_hooks/useAutoScrolling.js +16 -7
- package/es/hooks/useShallowStable.d.ts +32 -0
- package/es/hooks/useShallowStable.js +54 -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 +51 -19
- package/lib/components/Chart/Bubble/index.js +20 -6
- package/lib/components/Chart/Common/index.js +6 -1
- package/lib/components/Chart/Gauge/index.js +6 -1
- package/lib/components/Chart/Heatmap/index.js +23 -7
- package/lib/components/Chart/Line/index.js +51 -19
- package/lib/components/Chart/Pie/Pie3D/index.js +29 -12
- package/lib/components/Chart/Pie/index.js +23 -7
- package/lib/components/Chart/Polar/index.js +9 -2
- package/lib/components/Chart/Radar/index.js +6 -1
- package/lib/components/Chart/Sankey/index.js +10 -2
- package/lib/components/Chart/Tree/index.js +10 -2
- package/lib/components/Select/_hooks/useSelectComposition.js +6 -2
- package/lib/components/Table/_hooks/useAutoScrolling.js +16 -7
- package/lib/hooks/useShallowStable.d.ts +32 -0
- package/lib/hooks/useShallowStable.js +59 -0
- package/lib/layout/Menu/_hooks/useOnlyLvOneMenu.js +8 -2
- package/package.json +1 -1
- package/src/__tests__/callbackDepsGuard.test.ts +72 -0
- package/src/__tests__/depsScan.ts +158 -0
- package/src/__tests__/freshObjectDepsGuard.test.ts +144 -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 +65 -22
- package/src/components/Chart/Bubble/index.tsx +21 -6
- package/src/components/Chart/Common/index.tsx +6 -1
- package/src/components/Chart/Gauge/index.tsx +6 -1
- package/src/components/Chart/Heatmap/index.tsx +39 -17
- package/src/components/Chart/Line/index.tsx +65 -22
- package/src/components/Chart/Pie/Pie3D/index.tsx +27 -8
- package/src/components/Chart/Pie/index.tsx +25 -7
- package/src/components/Chart/Polar/index.tsx +10 -2
- package/src/components/Chart/Radar/index.tsx +6 -1
- 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/Chart/optionMemo.test.tsx +232 -0
- package/src/components/Select/_hooks/useSelectComposition.test.ts +47 -0
- package/src/components/Select/_hooks/useSelectComposition.ts +6 -2
- package/src/components/Table/_hooks/useAutoScrolling.test.tsx +95 -0
- package/src/components/Table/_hooks/useAutoScrolling.ts +16 -7
- package/src/hooks/useShallowStable.ts +65 -0
- package/src/layout/Menu/_hooks/useOnlyLvOneMenu.test.tsx +56 -0
- package/src/layout/Menu/_hooks/useOnlyLvOneMenu.ts +8 -2
|
@@ -9,7 +9,15 @@ import { useMouseOverHandler, useGlobalOutHandler } from "./_hooks";
|
|
|
9
9
|
import { getPie3DOption } from "./_utils/option";
|
|
10
10
|
import styles from "../../index.module.scss";
|
|
11
11
|
import { useLatestRef } from "../../../../hooks/useLatestRef";
|
|
12
|
+
import useShallowStable from "../../../../hooks/useShallowStable";
|
|
12
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
// 解构默认值写成字面量会每次渲染新建一个对象,进依赖数组就让 memo 恒不命中;提到模块级常量
|
|
15
|
+
var DEFAULT_TOOLTIP = {
|
|
16
|
+
show: false
|
|
17
|
+
};
|
|
18
|
+
var DEFAULT_LABEL = {
|
|
19
|
+
show: true
|
|
20
|
+
};
|
|
13
21
|
var ChartPie3D = function ChartPie3D(props) {
|
|
14
22
|
var className = props.className,
|
|
15
23
|
style = props.style,
|
|
@@ -33,18 +41,18 @@ var ChartPie3D = function ChartPie3D(props) {
|
|
|
33
41
|
_props$yOffset = props.yOffset,
|
|
34
42
|
yOffset = _props$yOffset === void 0 ? -0.2 : _props$yOffset,
|
|
35
43
|
_props$tooltip = props.tooltip,
|
|
36
|
-
tooltip = _props$tooltip === void 0 ?
|
|
37
|
-
show: false
|
|
38
|
-
} : _props$tooltip,
|
|
44
|
+
tooltip = _props$tooltip === void 0 ? DEFAULT_TOOLTIP : _props$tooltip,
|
|
39
45
|
_props$label = props.label,
|
|
40
|
-
label = _props$label === void 0 ?
|
|
41
|
-
show: true
|
|
42
|
-
} : _props$label,
|
|
46
|
+
label = _props$label === void 0 ? DEFAULT_LABEL : _props$label,
|
|
43
47
|
_props$enableMouseCon = props.enableMouseControl,
|
|
44
48
|
enableMouseControl = _props$enableMouseCon === void 0 ? false : _props$enableMouseCon,
|
|
45
49
|
onChart = props.onChart,
|
|
46
50
|
onClick = props.onClick,
|
|
47
|
-
|
|
51
|
+
restOption = _objectWithoutProperties(props, _excluded);
|
|
52
|
+
// rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 chartOption 的 memo
|
|
53
|
+
// 恒不命中 —— 父组件每渲染一次就重跑一次 setOption(notMerge),动画重播、悬浮态被清掉。
|
|
54
|
+
// useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
|
|
55
|
+
var coreOption = useShallowStable(restOption);
|
|
48
56
|
var chartRef = useRef(null);
|
|
49
57
|
var chartInstanceRef = useRef(null);
|
|
50
58
|
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
@@ -92,6 +100,12 @@ var ChartPie3D = function ChartPie3D(props) {
|
|
|
92
100
|
});
|
|
93
101
|
var onChartRef = useLatestRef(onChart);
|
|
94
102
|
|
|
103
|
+
// onClick 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 跟着重跑会重建
|
|
104
|
+
// 3D 饼的 option 并重播动画。注册与否看布尔量(保证「从无到有传入」仍会注册),
|
|
105
|
+
// 实际调用取 ref 里的最新引用。
|
|
106
|
+
var onClickRef = useLatestRef(onClick);
|
|
107
|
+
var hasOnClick = !!onClick;
|
|
108
|
+
|
|
95
109
|
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
96
110
|
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
97
111
|
useEffect(function () {
|
|
@@ -112,20 +126,24 @@ var ChartPie3D = function ChartPie3D(props) {
|
|
|
112
126
|
}
|
|
113
127
|
chart.on("mouseover", handleMouseOver);
|
|
114
128
|
chart.on("globalout", handleGlobalOut);
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
var handleClick = function handleClick(event) {
|
|
130
|
+
var _onClickRef$current;
|
|
131
|
+
(_onClickRef$current = onClickRef.current) === null || _onClickRef$current === void 0 || _onClickRef$current.call(onClickRef, event);
|
|
132
|
+
};
|
|
133
|
+
if (hasOnClick) {
|
|
134
|
+
chart.on("click", handleClick);
|
|
117
135
|
}
|
|
118
136
|
return function () {
|
|
119
137
|
window.removeEventListener("resize", handleResize);
|
|
120
138
|
if (chart) {
|
|
121
139
|
chart.off("mouseover", handleMouseOver);
|
|
122
140
|
chart.off("globalout", handleGlobalOut);
|
|
123
|
-
if (
|
|
124
|
-
chart.off("click",
|
|
141
|
+
if (hasOnClick) {
|
|
142
|
+
chart.off("click", handleClick);
|
|
125
143
|
}
|
|
126
144
|
}
|
|
127
145
|
};
|
|
128
|
-
}, [chartOption, handleResize, handleMouseOver, handleGlobalOut, onChartRef,
|
|
146
|
+
}, [chartOption, handleResize, handleMouseOver, handleGlobalOut, onChartRef, onClickRef, hasOnClick, containerReady]);
|
|
129
147
|
useEffect(function () {
|
|
130
148
|
return function () {
|
|
131
149
|
if (resizeObserverRef.current) {
|
|
@@ -20,7 +20,10 @@ import { autoScrollLegend } from "../chartUtils";
|
|
|
20
20
|
import styles from "../index.module.scss";
|
|
21
21
|
import ChartPie3D from "./Pie3D";
|
|
22
22
|
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
23
|
+
import useShallowStable from "../../../hooks/useShallowStable";
|
|
23
24
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
25
|
+
// 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
|
|
26
|
+
var EMPTY_EXTEND_SERIES = [];
|
|
24
27
|
var ChartPie = function ChartPie(props) {
|
|
25
28
|
var className = props.className,
|
|
26
29
|
style = props.style,
|
|
@@ -32,7 +35,7 @@ var ChartPie = function ChartPie(props) {
|
|
|
32
35
|
title = props.title,
|
|
33
36
|
onChart = props.onChart,
|
|
34
37
|
_props$extendSeries = props.extendSeries,
|
|
35
|
-
extendSeries = _props$extendSeries === void 0 ?
|
|
38
|
+
extendSeries = _props$extendSeries === void 0 ? EMPTY_EXTEND_SERIES : _props$extendSeries,
|
|
36
39
|
onClick = props.onClick,
|
|
37
40
|
_props$enableLegendAu = props.enableLegendAutoScroll,
|
|
38
41
|
enableLegendAutoScroll = _props$enableLegendAu === void 0 ? false : _props$enableLegendAu,
|
|
@@ -48,7 +51,11 @@ var ChartPie = function ChartPie(props) {
|
|
|
48
51
|
spanAngle = _props$spanAngle === void 0 ? 180 : _props$spanAngle,
|
|
49
52
|
center = props.center,
|
|
50
53
|
radius = props.radius,
|
|
51
|
-
|
|
54
|
+
restOption = _objectWithoutProperties(props, _excluded);
|
|
55
|
+
// rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 chartOption 的 memo
|
|
56
|
+
// 恒不命中 —— 父组件每渲染一次就重跑一次 setOption(notMerge),动画重播、悬浮态被清掉。
|
|
57
|
+
// useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
|
|
58
|
+
var coreOption = useShallowStable(restOption);
|
|
52
59
|
var chartRef = useRef(null);
|
|
53
60
|
var chartInstanceRef = useRef(null);
|
|
54
61
|
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
@@ -210,6 +217,12 @@ var ChartPie = function ChartPie(props) {
|
|
|
210
217
|
}, []);
|
|
211
218
|
var onChartRef = useLatestRef(onChart);
|
|
212
219
|
|
|
220
|
+
// onClick 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 跟着重跑就会
|
|
221
|
+
// 再走一遍 setOption(notMerge),动画重播、悬浮/高亮态被清掉。注册与否看布尔量,
|
|
222
|
+
// 这样「从无到有传入 onClick」仍会注册;实际调用取 ref 里的最新引用。
|
|
223
|
+
var onClickRef = useLatestRef(onClick);
|
|
224
|
+
var hasOnClick = !!onClick;
|
|
225
|
+
|
|
213
226
|
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
214
227
|
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
215
228
|
// Initialize the chart
|
|
@@ -239,9 +252,13 @@ var ChartPie = function ChartPie(props) {
|
|
|
239
252
|
}
|
|
240
253
|
|
|
241
254
|
// Add click event
|
|
242
|
-
|
|
255
|
+
var handleClick = function handleClick(event) {
|
|
256
|
+
var _onClickRef$current;
|
|
257
|
+
(_onClickRef$current = onClickRef.current) === null || _onClickRef$current === void 0 || _onClickRef$current.call(onClickRef, event);
|
|
258
|
+
};
|
|
259
|
+
if (hasOnClick) {
|
|
243
260
|
var _chartInstanceRef$cur2;
|
|
244
|
-
(_chartInstanceRef$cur2 = chartInstanceRef.current) === null || _chartInstanceRef$cur2 === void 0 || _chartInstanceRef$cur2.on("click",
|
|
261
|
+
(_chartInstanceRef$cur2 = chartInstanceRef.current) === null || _chartInstanceRef$cur2 === void 0 || _chartInstanceRef$cur2.on("click", handleClick);
|
|
245
262
|
}
|
|
246
263
|
|
|
247
264
|
// Legend auto-scroll
|
|
@@ -262,9 +279,9 @@ var ChartPie = function ChartPie(props) {
|
|
|
262
279
|
// Cleanup function
|
|
263
280
|
return function () {
|
|
264
281
|
window.removeEventListener("resize", handleResize);
|
|
265
|
-
if (
|
|
282
|
+
if (hasOnClick) {
|
|
266
283
|
var _chartInstanceRef$cur3;
|
|
267
|
-
(_chartInstanceRef$cur3 = chartInstanceRef.current) === null || _chartInstanceRef$cur3 === void 0 || _chartInstanceRef$cur3.off("click",
|
|
284
|
+
(_chartInstanceRef$cur3 = chartInstanceRef.current) === null || _chartInstanceRef$cur3 === void 0 || _chartInstanceRef$cur3.off("click", handleClick);
|
|
268
285
|
}
|
|
269
286
|
|
|
270
287
|
// Clean up legend scrolling
|
|
@@ -273,7 +290,7 @@ var ChartPie = function ChartPie(props) {
|
|
|
273
290
|
legendScrollRef.current = null;
|
|
274
291
|
}
|
|
275
292
|
};
|
|
276
|
-
}, [chartOption, handleResize, onChartRef,
|
|
293
|
+
}, [chartOption, handleResize, onChartRef, onClickRef, hasOnClick, enableLegendAutoScroll, seriesData, legendVisibleCount, legendScrollInterval, containerReady]);
|
|
277
294
|
|
|
278
295
|
// Clean up resources when the component unmounts
|
|
279
296
|
useEffect(function () {
|
|
@@ -11,7 +11,10 @@ import * as echarts from "echarts";
|
|
|
11
11
|
import useContainerReady from "../_hooks/useContainerReady";
|
|
12
12
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
13
13
|
import styles from "../index.module.scss";
|
|
14
|
+
import useShallowStable from "../../../hooks/useShallowStable";
|
|
14
15
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
16
|
+
// 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
|
|
17
|
+
var DEFAULT_COLOR = ["#00FFA3", "#00FFA3"];
|
|
15
18
|
var Polar = function Polar(props) {
|
|
16
19
|
var className = props.className,
|
|
17
20
|
style = props.style,
|
|
@@ -26,8 +29,12 @@ var Polar = function Polar(props) {
|
|
|
26
29
|
textStyle = props.textStyle,
|
|
27
30
|
seriesData = props.seriesData,
|
|
28
31
|
_props$color = props.color,
|
|
29
|
-
color = _props$color === void 0 ?
|
|
30
|
-
|
|
32
|
+
color = _props$color === void 0 ? DEFAULT_COLOR : _props$color,
|
|
33
|
+
restOption = _objectWithoutProperties(props, _excluded);
|
|
34
|
+
// rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 chartOption 的 memo
|
|
35
|
+
// 恒不命中 —— 父组件每渲染一次就重跑一次 setOption(notMerge),动画重播、悬浮态被清掉。
|
|
36
|
+
// useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
|
|
37
|
+
var coreOption = useShallowStable(restOption);
|
|
31
38
|
var chartRef = useRef(null);
|
|
32
39
|
var chartInstanceRef = useRef(null);
|
|
33
40
|
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
@@ -17,6 +17,7 @@ import * as echarts from "echarts";
|
|
|
17
17
|
import useContainerReady from "../_hooks/useContainerReady";
|
|
18
18
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
19
19
|
import styles from "../index.module.scss";
|
|
20
|
+
import useShallowStable from "../../../hooks/useShallowStable";
|
|
20
21
|
|
|
21
22
|
/** Single-indicator tooltip trigger mode
|
|
22
23
|
* - `point` triggers only when hovering data points/lines (native echarts item trigger)
|
|
@@ -38,7 +39,11 @@ var Radar = function Radar(props) {
|
|
|
38
39
|
singleIndicatorTooltipProp = _props$singleIndicato === void 0 ? false : _props$singleIndicato,
|
|
39
40
|
singleIndicatorMode = props.singleIndicatorMode,
|
|
40
41
|
singleIndicatorHoverFill = props.singleIndicatorHoverFill,
|
|
41
|
-
|
|
42
|
+
restOption = _objectWithoutProperties(props, _excluded);
|
|
43
|
+
// rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 chartOption 的 memo
|
|
44
|
+
// 恒不命中 —— 父组件每渲染一次就重跑一次 setOption(notMerge),动画重播、悬浮态被清掉。
|
|
45
|
+
// useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
|
|
46
|
+
var coreOption = useShallowStable(restOption);
|
|
42
47
|
var chartRef = useRef(null);
|
|
43
48
|
var chartInstanceRef = useRef(null);
|
|
44
49
|
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
@@ -14,6 +14,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
|
14
14
|
import useContainerReady from "../_hooks/useContainerReady";
|
|
15
15
|
import styles from "../index.module.scss";
|
|
16
16
|
import * as echarts from "echarts";
|
|
17
|
+
import useLatestRef from "../../../hooks/useLatestRef";
|
|
17
18
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
19
|
var Sankey = function Sankey(props) {
|
|
19
20
|
var className = props.className,
|
|
@@ -103,6 +104,13 @@ var Sankey = function Sankey(props) {
|
|
|
103
104
|
return option;
|
|
104
105
|
}, [seriesData, seriesLinks, series]);
|
|
105
106
|
|
|
107
|
+
// getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
|
|
108
|
+
// 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
|
|
109
|
+
// → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
|
|
110
|
+
// 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
|
|
111
|
+
// 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
|
|
112
|
+
var getImageRef = useLatestRef(getImage);
|
|
113
|
+
|
|
106
114
|
// Callback handling chart resize
|
|
107
115
|
var handleResize = useCallback(function () {
|
|
108
116
|
var _chartInstanceRef$cur;
|
|
@@ -125,7 +133,8 @@ var Sankey = function Sankey(props) {
|
|
|
125
133
|
|
|
126
134
|
// Attach the finished event listener on first initialization
|
|
127
135
|
chart.on("finished", function () {
|
|
128
|
-
|
|
136
|
+
var _getImageRef$current;
|
|
137
|
+
(_getImageRef$current = getImageRef.current) === null || _getImageRef$current === void 0 || _getImageRef$current.call(getImageRef, chart.getDataURL({
|
|
129
138
|
type: "png",
|
|
130
139
|
pixelRatio: 1,
|
|
131
140
|
backgroundColor: "#fff"
|
|
@@ -149,7 +158,7 @@ var Sankey = function Sankey(props) {
|
|
|
149
158
|
return function () {
|
|
150
159
|
window.removeEventListener("resize", handleResize);
|
|
151
160
|
};
|
|
152
|
-
}, [chartOption, handleResize,
|
|
161
|
+
}, [chartOption, handleResize, getImageRef, containerReady]);
|
|
153
162
|
|
|
154
163
|
// Clean up resources on unmount
|
|
155
164
|
useEffect(function () {
|
|
@@ -8,6 +8,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
|
8
8
|
import useContainerReady from "../_hooks/useContainerReady";
|
|
9
9
|
import styles from "../index.module.scss";
|
|
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
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -38,6 +39,13 @@ var Tree = function Tree(props) {
|
|
|
38
39
|
return option;
|
|
39
40
|
}, [seriesData, series]);
|
|
40
41
|
|
|
42
|
+
// getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
|
|
43
|
+
// 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
|
|
44
|
+
// → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
|
|
45
|
+
// 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
|
|
46
|
+
// 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
|
|
47
|
+
var getImageRef = useLatestRef(getImage);
|
|
48
|
+
|
|
41
49
|
// Callback handling chart resize
|
|
42
50
|
var handleResize = useCallback(function () {
|
|
43
51
|
var _chartInstanceRef$cur;
|
|
@@ -60,7 +68,8 @@ var Tree = function Tree(props) {
|
|
|
60
68
|
|
|
61
69
|
// Attach the finished event listener on first initialization
|
|
62
70
|
chart.on("finished", function () {
|
|
63
|
-
|
|
71
|
+
var _getImageRef$current;
|
|
72
|
+
(_getImageRef$current = getImageRef.current) === null || _getImageRef$current === void 0 || _getImageRef$current.call(getImageRef, chart.getDataURL({
|
|
64
73
|
type: "png",
|
|
65
74
|
pixelRatio: 1,
|
|
66
75
|
backgroundColor: "#fff"
|
|
@@ -84,7 +93,7 @@ var Tree = function Tree(props) {
|
|
|
84
93
|
return function () {
|
|
85
94
|
window.removeEventListener("resize", handleResize);
|
|
86
95
|
};
|
|
87
|
-
}, [chartOption, handleResize,
|
|
96
|
+
}, [chartOption, handleResize, getImageRef, containerReady]);
|
|
88
97
|
|
|
89
98
|
// Clean up resources on unmount
|
|
90
99
|
useEffect(function () {
|
|
@@ -14,6 +14,7 @@ export function useSelectComposition(_ref) {
|
|
|
14
14
|
_useState2 = _slicedToArray(_useState, 2),
|
|
15
15
|
isComposing = _useState2[0],
|
|
16
16
|
setComposing = _useState2[1];
|
|
17
|
+
var hasOnSearch = !!onSearch;
|
|
17
18
|
useEffect(function () {
|
|
18
19
|
var compositionend = function compositionend() {
|
|
19
20
|
setComposing(false);
|
|
@@ -21,7 +22,7 @@ export function useSelectComposition(_ref) {
|
|
|
21
22
|
var compositionstart = function compositionstart() {
|
|
22
23
|
setComposing(true);
|
|
23
24
|
};
|
|
24
|
-
if (
|
|
25
|
+
if (hasOnSearch) {
|
|
25
26
|
window.addEventListener("compositionstart", compositionstart);
|
|
26
27
|
window.addEventListener("compositionend", compositionend);
|
|
27
28
|
}
|
|
@@ -29,7 +30,10 @@ export function useSelectComposition(_ref) {
|
|
|
29
30
|
window.removeEventListener("compositionstart", compositionstart);
|
|
30
31
|
window.removeEventListener("compositionend", compositionend);
|
|
31
32
|
};
|
|
32
|
-
|
|
33
|
+
// onSearch 在这里只被当作「要不要监听输入法事件」的真值判断,effect 体从不调它。
|
|
34
|
+
// 直接进依赖数组的话,消费方传内联箭头就会每渲染一次拆一次、装一次 window 监听;
|
|
35
|
+
// 只取「有没有传」这个布尔量,从无到有时照样会重新注册。
|
|
36
|
+
}, [hasOnSearch]);
|
|
33
37
|
return {
|
|
34
38
|
isComposing: isComposing
|
|
35
39
|
};
|
|
@@ -7,6 +7,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
|
|
|
7
7
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
8
8
|
import { useMutationObserver } from "ahooks";
|
|
9
9
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
10
11
|
var DEFAULT_INTERVAL = 2000;
|
|
11
12
|
var DEFAULT_SPEED = 25;
|
|
12
13
|
var SMOOTH_SCROLL_TICK = 10;
|
|
@@ -29,6 +30,13 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
29
30
|
autoScrollLoopMode = _props$autoScrollLoop2 === void 0 ? "reset" : _props$autoScrollLoop2,
|
|
30
31
|
_props$autoScrollingO = props.autoScrollingOffset,
|
|
31
32
|
autoScrollingOffset = _props$autoScrollingO === void 0 ? 0 : _props$autoScrollingO;
|
|
33
|
+
|
|
34
|
+
// onAutoScrollEndAdd 在这里身兼两职:既是「有没有加载更多」的开关,又是真正去加载的动作。
|
|
35
|
+
// 整个 rAF 循环(loop → 依赖它 → 启停 effect 依赖 loop)都挂在它的引用上,
|
|
36
|
+
// 消费方传内联箭头就会每渲染一次重启一次滚动循环 —— 表格滚到一半被打回起点。
|
|
37
|
+
// 拆成两半:开关看布尔量(从无到有传入时仍会正确接上加载更多),动作取 ref 里的最新引用。
|
|
38
|
+
var onAutoScrollEndAddRef = useLatestRef(onAutoScrollEndAdd);
|
|
39
|
+
var hasOnAutoScrollEndAdd = !!onAutoScrollEndAdd;
|
|
32
40
|
var validSpeed = autoScrollingSpeed > 0 ? autoScrollingSpeed : DEFAULT_SPEED;
|
|
33
41
|
var smoothStep = validSpeed * SMOOTH_SCROLL_TICK / 1000;
|
|
34
42
|
var _useState = useState(false),
|
|
@@ -260,11 +268,12 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
260
268
|
}, SMOOTH_SCROLL_TICK);
|
|
261
269
|
}, [bindHover, checkSeamlessReset, clearSmoothTimer, getBody, interval, isAtBottom, isBodyScrollable, isSeamless, smoothStep]);
|
|
262
270
|
var runLoadMore = useCallback(function () {
|
|
263
|
-
|
|
271
|
+
var loadMore = onAutoScrollEndAddRef.current;
|
|
272
|
+
if (!loadMore || pendingLoadMoreRef.current) return;
|
|
264
273
|
pendingLoadMoreRef.current = true;
|
|
265
274
|
phaseRef.current = "loading";
|
|
266
275
|
var token = ++loadTokenRef.current;
|
|
267
|
-
|
|
276
|
+
loadMore().then(function (hasMore) {
|
|
268
277
|
if (token !== loadTokenRef.current) return;
|
|
269
278
|
var currentBody = getBody();
|
|
270
279
|
if (hasMore) {
|
|
@@ -284,7 +293,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
284
293
|
if (token !== loadTokenRef.current) return;
|
|
285
294
|
pendingLoadMoreRef.current = false;
|
|
286
295
|
});
|
|
287
|
-
}, [autoScrollLoop, getBody,
|
|
296
|
+
}, [autoScrollLoop, getBody, onAutoScrollEndAddRef]);
|
|
288
297
|
var loop = useCallback(function () {
|
|
289
298
|
rafRef.current = requestAnimationFrame(loop);
|
|
290
299
|
if (!autoScrolling || !ready || !(dataSource !== null && dataSource !== void 0 && dataSource.length) || !(ref !== null && ref !== void 0 && ref.current)) {
|
|
@@ -316,7 +325,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
316
325
|
// In seamless mode the scroll never actually reaches the bottom; just keep scrolling
|
|
317
326
|
phaseRef.current = "idle";
|
|
318
327
|
} else if (isAtBottom(body)) {
|
|
319
|
-
if (
|
|
328
|
+
if (hasOnAutoScrollEndAdd) {
|
|
320
329
|
runLoadMore();
|
|
321
330
|
} else {
|
|
322
331
|
if (autoScrollLoop) {
|
|
@@ -362,7 +371,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
362
371
|
return;
|
|
363
372
|
}
|
|
364
373
|
startSmoothStep(body, targetTop);
|
|
365
|
-
}, [autoScrollMode, autoScrollLoop, autoScrolling, bindHover, checkSeamlessReset, dataSource, getBody, getRowHeight, interval, isAtBottom, isBodyScrollable, isSeamless,
|
|
374
|
+
}, [autoScrollMode, autoScrollLoop, autoScrolling, bindHover, checkSeamlessReset, dataSource, getBody, getRowHeight, interval, isAtBottom, isBodyScrollable, isSeamless, hasOnAutoScrollEndAdd, ready, ref, runLoadMore, setupSeamlessClones, startSmoothStep]);
|
|
366
375
|
useEffect(function () {
|
|
367
376
|
if (!autoScrolling || !ready || !(dataSource !== null && dataSource !== void 0 && dataSource.length) || !(ref !== null && ref !== void 0 && ref.current)) {
|
|
368
377
|
cleanup();
|
|
@@ -387,7 +396,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
387
396
|
cleanupClones();
|
|
388
397
|
return;
|
|
389
398
|
}
|
|
390
|
-
if (
|
|
399
|
+
if (hasOnAutoScrollEndAdd) {
|
|
391
400
|
// In load-more mode, if scrolling had stopped, resume auto-scrolling when new data arrives
|
|
392
401
|
if (phaseRef.current === "stopped") {
|
|
393
402
|
phaseRef.current = autoScrollMode === "row" ? "wait" : "idle";
|
|
@@ -400,7 +409,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
|
|
|
400
409
|
phaseRef.current = autoScrollMode === "row" ? "wait" : "idle";
|
|
401
410
|
waitUntilRef.current = autoScrollMode === "row" ? performance.now() + interval : 0;
|
|
402
411
|
}
|
|
403
|
-
}, [autoScrollMode, cleanupClones, dataSource, getBody, interval, isBodyScrollable, isSeamless,
|
|
412
|
+
}, [autoScrollMode, cleanupClones, dataSource, getBody, interval, isBodyScrollable, isSeamless, hasOnAutoScrollEndAdd]);
|
|
404
413
|
useEffect(function () {
|
|
405
414
|
if (!autoScrolling || !(ref !== null && ref !== void 0 && ref.current)) return;
|
|
406
415
|
var resizeRaf = null;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把一个**内容没变、但每次渲染都新建**的对象收敛成**引用恒定**的对象:
|
|
3
|
+
* 浅比较(自有键 + `Object.is` 比值)相等就继续返回上一次那个引用。
|
|
4
|
+
*
|
|
5
|
+
* 用途只有一个:让它可以直接进 `useMemo` / `useEffect` 的依赖数组。
|
|
6
|
+
*
|
|
7
|
+
* React 的依赖数组按 `Object.is` 比引用,所以依赖里只要混进一个「每次渲染都新建
|
|
8
|
+
* 的对象」,memo 就**恒不命中**、effect 就**每渲染必重跑**。组件内部最容易出现
|
|
9
|
+
* 这种对象的地方是 rest 解构:
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { className, style, ...coreOption } = props; // coreOption 每次都是新对象
|
|
13
|
+
* const option = useMemo(() => ({ ...coreOption }), [coreOption]); // 永远不命中
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* 消费方就算把每一个 prop 都 memo 好,也躲不掉——新对象是组件自己建的。
|
|
17
|
+
* 正确写法是让这个对象回到值语义:
|
|
18
|
+
*
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { className, style, ...restOption } = props;
|
|
21
|
+
* const coreOption = useShallowStable(restOption); // 内容不变 → 引用不变
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* 注意这**不是**「缓存结果跳过副作用」:memo 该重算时照样重算、effect 该重跑时
|
|
25
|
+
* 照样重跑——只要 rest 里任何一个值真的变了,浅比较就不相等,新引用立刻透出去。
|
|
26
|
+
*
|
|
27
|
+
* 渲染期间读写 ref 的安全性:并发渲染下被丢弃的那次渲染可能已经把 ref 换成了它
|
|
28
|
+
* 自己的对象,但那个对象与当次 props 浅相等,所以后续渲染拿到的引用**内容永远
|
|
29
|
+
* 与当前 props 一致**,不会读到过期值。
|
|
30
|
+
*/
|
|
31
|
+
export declare function useShallowStable<T extends object>(value: T): T;
|
|
32
|
+
export default useShallowStable;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 把一个**内容没变、但每次渲染都新建**的对象收敛成**引用恒定**的对象:
|
|
5
|
+
* 浅比较(自有键 + `Object.is` 比值)相等就继续返回上一次那个引用。
|
|
6
|
+
*
|
|
7
|
+
* 用途只有一个:让它可以直接进 `useMemo` / `useEffect` 的依赖数组。
|
|
8
|
+
*
|
|
9
|
+
* React 的依赖数组按 `Object.is` 比引用,所以依赖里只要混进一个「每次渲染都新建
|
|
10
|
+
* 的对象」,memo 就**恒不命中**、effect 就**每渲染必重跑**。组件内部最容易出现
|
|
11
|
+
* 这种对象的地方是 rest 解构:
|
|
12
|
+
*
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { className, style, ...coreOption } = props; // coreOption 每次都是新对象
|
|
15
|
+
* const option = useMemo(() => ({ ...coreOption }), [coreOption]); // 永远不命中
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* 消费方就算把每一个 prop 都 memo 好,也躲不掉——新对象是组件自己建的。
|
|
19
|
+
* 正确写法是让这个对象回到值语义:
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const { className, style, ...restOption } = props;
|
|
23
|
+
* const coreOption = useShallowStable(restOption); // 内容不变 → 引用不变
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* 注意这**不是**「缓存结果跳过副作用」:memo 该重算时照样重算、effect 该重跑时
|
|
27
|
+
* 照样重跑——只要 rest 里任何一个值真的变了,浅比较就不相等,新引用立刻透出去。
|
|
28
|
+
*
|
|
29
|
+
* 渲染期间读写 ref 的安全性:并发渲染下被丢弃的那次渲染可能已经把 ref 换成了它
|
|
30
|
+
* 自己的对象,但那个对象与当次 props 浅相等,所以后续渲染拿到的引用**内容永远
|
|
31
|
+
* 与当前 props 一致**,不会读到过期值。
|
|
32
|
+
*/
|
|
33
|
+
export function useShallowStable(value) {
|
|
34
|
+
var ref = useRef(value);
|
|
35
|
+
if (!shallowEqual(ref.current, value)) {
|
|
36
|
+
ref.current = value;
|
|
37
|
+
}
|
|
38
|
+
return ref.current;
|
|
39
|
+
}
|
|
40
|
+
function shallowEqual(a, b) {
|
|
41
|
+
if (Object.is(a, b)) return true;
|
|
42
|
+
var aKeys = Object.keys(a);
|
|
43
|
+
var bKeys = Object.keys(b);
|
|
44
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
45
|
+
for (var _i = 0, _aKeys = aKeys; _i < _aKeys.length; _i++) {
|
|
46
|
+
var key = _aKeys[_i];
|
|
47
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
48
|
+
if (!Object.is(a[key], b[key])) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
export default useShallowStable;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect } from "react";
|
|
2
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
2
3
|
import { useLocation } from "react-router";
|
|
3
4
|
/**
|
|
4
5
|
* Handle the top-level-only menu logic
|
|
@@ -10,16 +11,22 @@ export var useOnlyLvOneMenu = function useOnlyLvOneMenu(_ref) {
|
|
|
10
11
|
setOpenkeys = _ref.setOpenkeys,
|
|
11
12
|
setMenuKey = _ref.setMenuKey;
|
|
12
13
|
var location = useLocation();
|
|
14
|
+
// getCurrChildItems 是 effect 体里发出去的通知,本身进依赖数组是这一类死循环的标准配方:
|
|
15
|
+
// 消费方传内联箭头 → 每次渲染新引用 → effect 重跑 → setOpenkeys([item.key]) 每次都是
|
|
16
|
+
// 新数组必定触发重渲染 → 又是新引用 …… 直到 React 抛 Maximum update depth exceeded。
|
|
17
|
+
// 通知取 ref 里的最新引用,依赖数组只留真正的输入。
|
|
18
|
+
var getCurrChildItemsRef = useLatestRef(getCurrChildItems);
|
|
13
19
|
useEffect(function () {
|
|
14
20
|
if (onlyLvOneMenu) {
|
|
15
21
|
var item = items.find(function (i) {
|
|
16
22
|
return i.key === "/" + location.pathname.split("/").filter(Boolean)[0];
|
|
17
23
|
});
|
|
18
24
|
if (item) {
|
|
19
|
-
|
|
25
|
+
var _getCurrChildItemsRef;
|
|
26
|
+
(_getCurrChildItemsRef = getCurrChildItemsRef.current) === null || _getCurrChildItemsRef === void 0 || _getCurrChildItemsRef.call(getCurrChildItemsRef, item.children || []);
|
|
20
27
|
setOpenkeys([item.key]);
|
|
21
28
|
setMenuKey(item.key);
|
|
22
29
|
}
|
|
23
30
|
}
|
|
24
|
-
}, [items,
|
|
31
|
+
}, [items, getCurrChildItemsRef, location, onlyLvOneMenu, setOpenkeys, setMenuKey]);
|
|
25
32
|
};
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.useChainGraphData = useChainGraphData;
|
|
7
7
|
var _react = require("react");
|
|
8
8
|
var _hsuUtils = require("hsu-utils");
|
|
9
|
+
var _useLatestRef = require("../../../hooks/useLatestRef");
|
|
9
10
|
function useChainGraphData(props) {
|
|
10
11
|
const {
|
|
11
12
|
graph,
|
|
@@ -18,6 +19,25 @@ function useChainGraphData(props) {
|
|
|
18
19
|
} = props;
|
|
19
20
|
const [lastData, setLastData] = (0, _react.useState)(undefined);
|
|
20
21
|
const [isLayouting, setIsLayouting] = (0, _react.useState)(true);
|
|
22
|
+
|
|
23
|
+
// 这三个回调都是「交给图实例、由它在后续事件里回调」的出口,不是数据本身。
|
|
24
|
+
// 它们进依赖数组是条死依赖:effect 体被 ObjEqual(lastData, data) 挡住,
|
|
25
|
+
// 引用变了也不会重新 setData —— 图里存的还是首次 setData 时那个闭包,
|
|
26
|
+
// 换了引用永远调不到(stale closure)。改成引用恒定的转发函数:
|
|
27
|
+
// 依赖数组只留真正的输入(graph/data/level/rootLevel),调用时总是取到最新的回调。
|
|
28
|
+
const getImageRef = (0, _useLatestRef.useLatestRef)(getImage);
|
|
29
|
+
const labelRenderRef = (0, _useLatestRef.useLatestRef)(labelRender);
|
|
30
|
+
const onLayoutingChangeRef = (0, _useLatestRef.useLatestRef)(onLayoutingChange);
|
|
31
|
+
const emitImage = (0, _react.useCallback)(img => {
|
|
32
|
+
getImageRef.current?.(img);
|
|
33
|
+
}, [getImageRef]);
|
|
34
|
+
// labelRender 在 ChainGraphServices.setData 里是「传了才改写 label」的真值判断,
|
|
35
|
+
// 所以这里保留「有没有传」的语义:没传就传 undefined,传了才给转发函数。
|
|
36
|
+
const renderLabel = (0, _react.useCallback)(label => labelRenderRef.current(label), [labelRenderRef]);
|
|
37
|
+
const emitLayouting = (0, _react.useCallback)(value => {
|
|
38
|
+
setIsLayouting(value);
|
|
39
|
+
onLayoutingChangeRef.current?.(value);
|
|
40
|
+
}, [onLayoutingChangeRef]);
|
|
21
41
|
(0, _react.useEffect)(() => {
|
|
22
42
|
if (graph && !_hsuUtils.Equal.ObjEqual(lastData, data)) {
|
|
23
43
|
setLastData(data);
|
|
@@ -25,15 +45,12 @@ function useChainGraphData(props) {
|
|
|
25
45
|
data,
|
|
26
46
|
level,
|
|
27
47
|
rootLevel,
|
|
28
|
-
getImage,
|
|
29
|
-
isLayouting:
|
|
30
|
-
|
|
31
|
-
onLayoutingChange?.(value);
|
|
32
|
-
},
|
|
33
|
-
labelRender
|
|
48
|
+
getImage: emitImage,
|
|
49
|
+
isLayouting: emitLayouting,
|
|
50
|
+
labelRender: labelRenderRef.current ? renderLabel : undefined
|
|
34
51
|
});
|
|
35
52
|
}
|
|
36
|
-
}, [graph, data, level, rootLevel,
|
|
53
|
+
}, [graph, data, level, rootLevel, lastData, emitImage, emitLayouting, renderLabel, labelRenderRef]);
|
|
37
54
|
return {
|
|
38
55
|
isLayouting
|
|
39
56
|
};
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.useChainGraphLayout = useChainGraphLayout;
|
|
7
7
|
var _react = require("react");
|
|
8
|
+
var _useLatestRef = require("../../../hooks/useLatestRef");
|
|
8
9
|
function useChainGraphLayout(props) {
|
|
9
10
|
const {
|
|
10
11
|
graph,
|
|
@@ -12,9 +13,18 @@ function useChainGraphLayout(props) {
|
|
|
12
13
|
rootLevel,
|
|
13
14
|
getImage
|
|
14
15
|
} = props;
|
|
16
|
+
|
|
17
|
+
// getImage 只是「布局完成后把截图 dataURL 交出去」的出口,不是布局的输入。
|
|
18
|
+
// 它进依赖数组会让消费方每传一次内联箭头就重新 changeLayout 一次 —— 整张图重排;
|
|
19
|
+
// 消费方再把截图存进 state,就变成「重排 → 出图 → setState → 新引用 → 再重排」的死循环。
|
|
20
|
+
// 用引用恒定的转发函数注册,调用时再取最新的 getImage。
|
|
21
|
+
const getImageRef = (0, _useLatestRef.useLatestRef)(getImage);
|
|
22
|
+
const emitImage = (0, _react.useCallback)(img => {
|
|
23
|
+
getImageRef.current?.(img);
|
|
24
|
+
}, [getImageRef]);
|
|
15
25
|
(0, _react.useEffect)(() => {
|
|
16
26
|
if (graph && octopus !== undefined) {
|
|
17
|
-
graph.changeLayout(octopus, rootLevel,
|
|
27
|
+
graph.changeLayout(octopus, rootLevel, emitImage);
|
|
18
28
|
}
|
|
19
|
-
}, [graph, octopus, rootLevel,
|
|
29
|
+
}, [graph, octopus, rootLevel, emitImage]);
|
|
20
30
|
}
|