@hsu-react/ui 2.5.3 → 2.5.4
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/Chart/Bubble/index.js +7 -2
- package/es/components/Chart/Common/index.js +7 -2
- package/es/components/Chart/Heatmap/index.js +7 -2
- package/es/components/Chart/Pie/Pie3D/index.js +8 -2
- package/es/components/Chart/Pie/index.js +7 -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/useSelectInputOptions.js +8 -2
- package/es/components/Slider/index.js +24 -15
- 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/lib/components/Chart/Bubble/index.js +6 -2
- package/lib/components/Chart/Common/index.js +6 -2
- package/lib/components/Chart/Heatmap/index.js +6 -2
- package/lib/components/Chart/Pie/Pie3D/index.js +7 -2
- package/lib/components/Chart/Pie/index.js +6 -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/useSelectInputOptions.js +7 -2
- package/lib/components/Slider/index.js +23 -11
- 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/package.json +9 -3
- package/src/__tests__/setup.ts +26 -0
- package/src/components/Chart/Bubble/index.tsx +7 -2
- package/src/components/Chart/Common/index.tsx +7 -2
- package/src/components/Chart/Heatmap/index.tsx +7 -2
- package/src/components/Chart/Pie/Pie3D/index.tsx +7 -2
- package/src/components/Chart/Pie/index.tsx +7 -2
- 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/useSelectInputOptions.ts +6 -9
- package/src/components/Slider/index.tsx +22 -12
- 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
|
@@ -17,6 +17,7 @@ import AntdTextArea from "antd/es/input/TextArea";
|
|
|
17
17
|
import React, { useEffect, useRef, useState } from "react";
|
|
18
18
|
import classNames from "classnames";
|
|
19
19
|
import styles from "./index.module.scss";
|
|
20
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
20
21
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
21
22
|
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
22
23
|
var TextArea = function TextArea(props) {
|
|
@@ -40,56 +41,74 @@ var TextArea = function TextArea(props) {
|
|
|
40
41
|
autoSize = props.autoSize,
|
|
41
42
|
style = props.style,
|
|
42
43
|
inputConfig = _objectWithoutProperties(props, _excluded);
|
|
44
|
+
var ref = useRef(null);
|
|
43
45
|
var _useState = useState(false),
|
|
44
46
|
_useState2 = _slicedToArray(_useState, 2),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
var ref = useRef(null);
|
|
48
|
-
var _useState3 = useState(false),
|
|
49
|
-
_useState4 = _slicedToArray(_useState3, 2),
|
|
50
|
-
focused = _useState4[0],
|
|
51
|
-
setFocused = _useState4[1];
|
|
47
|
+
focused = _useState2[0],
|
|
48
|
+
setFocused = _useState2[1];
|
|
52
49
|
|
|
53
50
|
// On initialization, prefer value, then fall back to defaultValue
|
|
54
51
|
var initialValue = value !== undefined ? (_value$toString = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString !== void 0 ? _value$toString : "" : defaultValue !== undefined ? (_defaultValue$toStrin = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.toString()) !== null && _defaultValue$toStrin !== void 0 ? _defaultValue$toStrin : "" : "";
|
|
55
|
-
var
|
|
52
|
+
var _useState3 = useState(initialValue),
|
|
53
|
+
_useState4 = _slicedToArray(_useState3, 2),
|
|
54
|
+
_value = _useState4[0],
|
|
55
|
+
setValue = _useState4[1];
|
|
56
|
+
var _useState5 = useState(false),
|
|
56
57
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
var _useState7 = useState(initialValue),
|
|
60
|
-
_useState8 = _slicedToArray(_useState7, 2),
|
|
61
|
-
lastValue = _useState8[0],
|
|
62
|
-
setLastValue = _useState8[1];
|
|
63
|
-
var _useState9 = useState(false),
|
|
64
|
-
_useState10 = _slicedToArray(_useState9, 2),
|
|
65
|
-
hasError = _useState10[0],
|
|
66
|
-
setHasError = _useState10[1];
|
|
58
|
+
hasError = _useState6[0],
|
|
59
|
+
setHasError = _useState6[1];
|
|
67
60
|
var prevValueRef = useRef(undefined);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
var getRefRef = useLatestRef(getRef);
|
|
62
|
+
|
|
63
|
+
// 输入法组字期间不往外通知。放 ref 不放 state:它只影响「要不要发通知」,
|
|
64
|
+
// 不参与渲染,而且必须在同一次事件里立刻生效
|
|
65
|
+
var composingRef = useRef(false);
|
|
66
|
+
// 最近一次通知出去的原文。只用来去掉 compositionend 与 input 两个事件的重复通知
|
|
67
|
+
var notifiedRef = useRef(initialValue);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
|
|
71
|
+
* 所以它在事件处理里发,不在 effect 里发。
|
|
72
|
+
*
|
|
73
|
+
* 早先是一个 effect 在发:依赖数组里放着 `onChange`,effect 体内又调它,
|
|
74
|
+
* 还用一个 `lastValue` state 记「这个值通知过没有」。两处都是错的——
|
|
75
|
+
* 1. 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
|
|
76
|
+
* 每次渲染都是新引用,effect 每次渲染都重跑;
|
|
77
|
+
* 2. 「通知过没有」的记账放在 React state 里就**不可靠**:父级用 MobX /
|
|
78
|
+
* `useSyncExternalStore` 这类外部 store 回写 `value` 时,React 会以更高的
|
|
79
|
+
* 同步优先级重渲一次,effect 里排的那次 `setLastValue` 会被这一趟**跳过**,
|
|
80
|
+
* 于是 effect 读到过期的记账、判定「还没通知」,再发一次 → 父级再回写 →
|
|
81
|
+
* 死循环,React 抛 `Maximum update depth exceeded`,输入框被上层
|
|
82
|
+
* ErrorBoundary 整块摘掉。
|
|
83
|
+
*
|
|
84
|
+
* 现在 `onChange` 的引用稳不稳定完全不影响正确性:永远调到最新的那个回调,
|
|
85
|
+
* 且只在真的有新文本时调一次。
|
|
86
|
+
*/
|
|
87
|
+
var notify = function notify(next) {
|
|
88
|
+
if (next === notifiedRef.current) return;
|
|
89
|
+
notifiedRef.current = next;
|
|
90
|
+
// 全是空白等同于空:外部拿到的是 "",但输入框里用户敲的空格照旧留着
|
|
91
|
+
onChange === null || onChange === void 0 || onChange(next.trim() === "" ? "" : next);
|
|
92
|
+
};
|
|
76
93
|
useEffect(function () {
|
|
77
94
|
// Update internal state only when the external value prop actually changes
|
|
78
95
|
if (prevValueRef.current !== value) {
|
|
96
|
+
var _value$toString2;
|
|
79
97
|
prevValueRef.current = value;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
setValue("");
|
|
86
|
-
setLastValue("");
|
|
87
|
-
}
|
|
98
|
+
|
|
99
|
+
// 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
|
|
100
|
+
var next = value !== undefined ? (_value$toString2 = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString2 !== void 0 ? _value$toString2 : "" : "";
|
|
101
|
+
notifiedRef.current = next;
|
|
102
|
+
setValue(next);
|
|
88
103
|
}
|
|
89
104
|
}, [value]);
|
|
105
|
+
|
|
106
|
+
// 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑,
|
|
107
|
+
// 消费方在里面 setState 就是一个死循环
|
|
90
108
|
useEffect(function () {
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
var _getRefRef$current;
|
|
110
|
+
(_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
|
|
111
|
+
}, [getRefRef]);
|
|
93
112
|
useEffect(function () {
|
|
94
113
|
var _ref$current2;
|
|
95
114
|
// Use a MutationObserver to watch for error state changes
|
|
@@ -124,19 +143,22 @@ var TextArea = function TextArea(props) {
|
|
|
124
143
|
children: [prefix, /*#__PURE__*/_jsx(AntdTextArea, _objectSpread({
|
|
125
144
|
ref: ref,
|
|
126
145
|
onCompositionStart: function onCompositionStart() {
|
|
127
|
-
|
|
146
|
+
composingRef.current = true;
|
|
128
147
|
},
|
|
129
|
-
onCompositionEnd: function onCompositionEnd() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
onCompositionEnd: function onCompositionEnd(e) {
|
|
149
|
+
composingRef.current = false;
|
|
150
|
+
// 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
|
|
151
|
+
// 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
|
|
152
|
+
notify(e.currentTarget.value);
|
|
133
153
|
},
|
|
134
154
|
value: _value,
|
|
135
155
|
autoSize: disabled && text ? autoSize || {
|
|
136
156
|
minRows: 1
|
|
137
157
|
} : autoSize,
|
|
138
158
|
onChange: function onChange(e) {
|
|
139
|
-
|
|
159
|
+
var next = e.target.value;
|
|
160
|
+
setValue(next);
|
|
161
|
+
if (!composingRef.current) notify(next);
|
|
140
162
|
},
|
|
141
163
|
onFocus: function onFocus(e) {
|
|
142
164
|
setFocused(true);
|
|
@@ -22,7 +22,7 @@ import TextArea from "./TextArea";
|
|
|
22
22
|
import classNames from "classnames";
|
|
23
23
|
import styles from "./index.module.scss";
|
|
24
24
|
import RangeInput from "./Range";
|
|
25
|
-
import {
|
|
25
|
+
import { useLatestRef } from "../../hooks/useLatestRef";
|
|
26
26
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
27
27
|
var Input = function Input(props) {
|
|
28
28
|
var onChange = props.onChange,
|
|
@@ -39,11 +39,11 @@ var Input = function Input(props) {
|
|
|
39
39
|
tooltip = props.tooltip,
|
|
40
40
|
escapeCharacters = props.escapeCharacters,
|
|
41
41
|
inputConfig = _objectWithoutProperties(props, _excluded);
|
|
42
|
-
var _useState = useState(false),
|
|
43
|
-
_useState2 = _slicedToArray(_useState, 2),
|
|
44
|
-
isComposing = _useState2[0],
|
|
45
|
-
setComposing = _useState2[1];
|
|
46
42
|
var ref = useRef(null);
|
|
43
|
+
var getRefRef = useLatestRef(getRef);
|
|
44
|
+
// 输入法组字期间不往外通知。放 ref 不放 state:它只决定「要不要发通知」,
|
|
45
|
+
// 不参与渲染,而且必须在同一次事件里立刻生效
|
|
46
|
+
var composingRef = useRef(false);
|
|
47
47
|
|
|
48
48
|
// Handle escaping: if the value contains characters listed in escapeCharacters, prefix them with an escape
|
|
49
49
|
var escapeValue = useCallback(function (val) {
|
|
@@ -112,27 +112,27 @@ var Input = function Input(props) {
|
|
|
112
112
|
return unescapeValue(rawValue);
|
|
113
113
|
};
|
|
114
114
|
var initialValue = getInitialValue();
|
|
115
|
-
var
|
|
116
|
-
|
|
117
|
-
_value =
|
|
118
|
-
setValue =
|
|
119
|
-
var _useState5 = useState(initialValue),
|
|
120
|
-
_useState6 = _slicedToArray(_useState5, 2),
|
|
121
|
-
lastValue = _useState6[0],
|
|
122
|
-
setLastValue = _useState6[1];
|
|
115
|
+
var _useState = useState(initialValue),
|
|
116
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
117
|
+
_value = _useState2[0],
|
|
118
|
+
setValue = _useState2[1];
|
|
123
119
|
var prevValueRef = useRef(undefined);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
120
|
+
// 最近一次通知出去的原文(未转义)。只用来去掉 compositionend 与 input
|
|
121
|
+
// 两个事件的重复通知
|
|
122
|
+
var notifiedRef = useRef(initialValue);
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
|
|
126
|
+
* 所以在事件处理里发,不在 effect 里发。详见 `TextArea/index.tsx` 里那段说明——
|
|
127
|
+
* 旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
|
|
128
|
+
* 会让传内联箭头的消费方死循环。
|
|
129
|
+
*/
|
|
130
|
+
var notify = function notify(next) {
|
|
131
|
+
if (next === notifiedRef.current) return;
|
|
132
|
+
notifiedRef.current = next;
|
|
133
|
+
// 全是空白等同于空;escapeCharacters 命中时对外给转义后的值
|
|
134
|
+
onChange === null || onChange === void 0 || onChange(escapeValue(next.trim() === "" ? "" : next));
|
|
135
|
+
};
|
|
136
136
|
useEffect(function () {
|
|
137
137
|
// Update internal state only when the external value prop actually changes
|
|
138
138
|
if (prevValueRef.current !== value) {
|
|
@@ -141,18 +141,22 @@ var Input = function Input(props) {
|
|
|
141
141
|
var rawValue = typeof value === "number" ? "".concat(value) : value === null || value === void 0 ? void 0 : value.toString();
|
|
142
142
|
// Unescape before displaying
|
|
143
143
|
var newValue = unescapeValue(rawValue);
|
|
144
|
+
// 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
|
|
145
|
+
notifiedRef.current = newValue;
|
|
144
146
|
setValue(newValue);
|
|
145
|
-
setLastValue(newValue);
|
|
146
147
|
} else {
|
|
147
148
|
// Clear only on initialization or when explicitly set to undefined externally
|
|
149
|
+
notifiedRef.current = "";
|
|
148
150
|
setValue("");
|
|
149
|
-
setLastValue("");
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
}, [value, unescapeValue]);
|
|
154
|
+
|
|
155
|
+
// 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑
|
|
153
156
|
useEffect(function () {
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
var _getRefRef$current;
|
|
158
|
+
(_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
|
|
159
|
+
}, [getRefRef]);
|
|
156
160
|
return /*#__PURE__*/_jsx(Tooltip, _objectSpread(_objectSpread({
|
|
157
161
|
placement: "topLeft"
|
|
158
162
|
}, tooltip), {}, {
|
|
@@ -161,12 +165,13 @@ var Input = function Input(props) {
|
|
|
161
165
|
disabled: disabled,
|
|
162
166
|
placeholder: disabled ? "" : placeholder,
|
|
163
167
|
onCompositionStart: function onCompositionStart() {
|
|
164
|
-
|
|
168
|
+
composingRef.current = true;
|
|
165
169
|
},
|
|
166
|
-
onCompositionEnd: function onCompositionEnd() {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
onCompositionEnd: function onCompositionEnd(e) {
|
|
171
|
+
composingRef.current = false;
|
|
172
|
+
// 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
|
|
173
|
+
// 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
|
|
174
|
+
notify(unescapeValue(e.currentTarget.value));
|
|
170
175
|
},
|
|
171
176
|
value: _value,
|
|
172
177
|
onChange: function onChange(e) {
|
|
@@ -174,6 +179,7 @@ var Input = function Input(props) {
|
|
|
174
179
|
var inputValue = e.target.value;
|
|
175
180
|
var unescapedInput = unescapeValue(inputValue);
|
|
176
181
|
setValue(unescapedInput);
|
|
182
|
+
if (!composingRef.current) notify(unescapedInput);
|
|
177
183
|
},
|
|
178
184
|
className: classNames(styles.antdInput, className),
|
|
179
185
|
type: type,
|
|
@@ -5,6 +5,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
|
|
5
5
|
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
|
6
6
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
8
9
|
/**
|
|
9
10
|
* Manage input options (when there are no matching options)
|
|
10
11
|
*/
|
|
@@ -18,11 +19,16 @@ export function useSelectInputOptions(_ref) {
|
|
|
18
19
|
_useState2 = _slicedToArray(_useState, 2),
|
|
19
20
|
inputOptions = _useState2[0],
|
|
20
21
|
setInputOptions = _useState2[1];
|
|
22
|
+
var onSearchRef = useLatestRef(onSearch);
|
|
23
|
+
|
|
24
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
25
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
21
26
|
useEffect(function () {
|
|
22
27
|
if (!isComposing) {
|
|
23
|
-
|
|
28
|
+
var _onSearchRef$current;
|
|
29
|
+
(_onSearchRef$current = onSearchRef.current) === null || _onSearchRef$current === void 0 || _onSearchRef$current.call(onSearchRef, searchValue);
|
|
24
30
|
}
|
|
25
|
-
}, [inputOptions, isComposing,
|
|
31
|
+
}, [inputOptions, isComposing, onSearchRef, optionsLength, searchValue]);
|
|
26
32
|
return {
|
|
27
33
|
inputOptions: inputOptions,
|
|
28
34
|
setInputOptions: setInputOptions
|
|
@@ -14,7 +14,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
14
14
|
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
|
|
15
15
|
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
|
16
16
|
import { Slider as AntdSlider } from "antd";
|
|
17
|
-
import React, { useEffect, useState } from "react";
|
|
17
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
18
18
|
import classNames from "classnames";
|
|
19
19
|
import styles from "./index.module.scss";
|
|
20
20
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
@@ -29,28 +29,37 @@ var Slider = function Slider(props) {
|
|
|
29
29
|
_useState2 = _slicedToArray(_useState, 2),
|
|
30
30
|
_value = _useState2[0],
|
|
31
31
|
setValue = _useState2[1];
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// 最近一次通知出去的值,代替原来那个用 state 记账的写法
|
|
33
|
+
var notifiedRef = useRef(value);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* `onChange` 是「用户拖了滑块」这个**事件**的通知,不是从 state 推导出来的结果,
|
|
37
|
+
* 所以在事件处理里发,不在 effect 里发。详见 `Input/TextArea/index.tsx` 里那段
|
|
38
|
+
* 说明——旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
|
|
39
|
+
* 会让传内联箭头的消费方死循环。
|
|
40
|
+
*/
|
|
41
|
+
var handleChange = function handleChange(next) {
|
|
42
|
+
setValue(next);
|
|
43
|
+
if (next === notifiedRef.current) return;
|
|
44
|
+
notifiedRef.current = next;
|
|
45
|
+
onChange === null || onChange === void 0 || onChange(next);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// 外部值(`value` / `topValue`)变了才回写,不跟着通知走
|
|
36
49
|
useEffect(function () {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
var next = topValue || value;
|
|
51
|
+
if (next !== notifiedRef.current) {
|
|
52
|
+
notifiedRef.current = next;
|
|
53
|
+
setValue(next);
|
|
40
54
|
}
|
|
41
|
-
}, [
|
|
42
|
-
useEffect(function () {
|
|
43
|
-
if ((topValue || value) !== lastValue) {
|
|
44
|
-
setValue(topValue || value);
|
|
45
|
-
}
|
|
46
|
-
}, [lastValue, topValue, value]);
|
|
55
|
+
}, [topValue, value]);
|
|
47
56
|
return /*#__PURE__*/_jsx(AntdSlider, _objectSpread({
|
|
48
57
|
className: classNames([styles.slider, className]),
|
|
49
58
|
tooltip: {
|
|
50
59
|
open: false
|
|
51
60
|
},
|
|
52
61
|
value: _value,
|
|
53
|
-
onChange:
|
|
62
|
+
onChange: handleChange
|
|
54
63
|
}, sliderConfig));
|
|
55
64
|
};
|
|
56
65
|
export default Slider;
|
|
@@ -7,6 +7,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
8
|
import { deepCopy, Equal } from "hsu-utils";
|
|
9
9
|
import { extractFileUrl } from "../_utils";
|
|
10
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
10
11
|
/**
|
|
11
12
|
* Manage the upload file list state
|
|
12
13
|
*/
|
|
@@ -22,6 +23,10 @@ export function useUploadFileList(_ref) {
|
|
|
22
23
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
23
24
|
lastFileList = _useState4[0],
|
|
24
25
|
setLastFileList = _useState4[1];
|
|
26
|
+
var onChangeRef = useLatestRef(onChange);
|
|
27
|
+
|
|
28
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
29
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
25
30
|
useEffect(function () {
|
|
26
31
|
if (rmFile) {
|
|
27
32
|
var file = _fileList.find(function (item) {
|
|
@@ -33,14 +38,15 @@ export function useUploadFileList(_ref) {
|
|
|
33
38
|
if (file) {
|
|
34
39
|
setFilelist(filteredList);
|
|
35
40
|
setTimeout(function () {
|
|
36
|
-
|
|
41
|
+
var _onChangeRef$current;
|
|
42
|
+
(_onChangeRef$current = onChangeRef.current) === null || _onChangeRef$current === void 0 || _onChangeRef$current.call(onChangeRef, {
|
|
37
43
|
file: file,
|
|
38
44
|
fileList: filteredList
|
|
39
45
|
});
|
|
40
46
|
}, 100);
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
|
-
}, [_fileList,
|
|
49
|
+
}, [_fileList, onChangeRef, rmFile]);
|
|
44
50
|
useEffect(function () {
|
|
45
51
|
if (!Equal.ObjEqual(fileList !== null && fileList !== void 0 ? fileList : [], lastFileList)) {
|
|
46
52
|
if (fileList !== null && fileList !== void 0 && fileList.length) {
|
|
@@ -6,6 +6,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
|
|
|
6
6
|
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
|
7
7
|
import { useEffect, useRef, useState } from "react";
|
|
8
8
|
import { deepCopy, Equal } from "hsu-utils";
|
|
9
|
+
import { useLatestRef } from "../../../hooks/useLatestRef";
|
|
9
10
|
/**
|
|
10
11
|
* Manage upload and download operations
|
|
11
12
|
*/
|
|
@@ -27,6 +28,7 @@ export function useUploadOperations(_ref) {
|
|
|
27
28
|
downloading: {},
|
|
28
29
|
uploadingList: {}
|
|
29
30
|
});
|
|
31
|
+
var onUploadingListRef = useLatestRef(onUploadingList);
|
|
30
32
|
|
|
31
33
|
// Sync to the ref on update
|
|
32
34
|
useEffect(function () {
|
|
@@ -35,12 +37,16 @@ export function useUploadOperations(_ref) {
|
|
|
35
37
|
useEffect(function () {
|
|
36
38
|
operationsRef.current.uploadingList = uploadingList;
|
|
37
39
|
}, [uploadingList]);
|
|
40
|
+
|
|
41
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
42
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
38
43
|
useEffect(function () {
|
|
39
44
|
if (!Equal.ObjEqual(uploadingList, lastUploadList)) {
|
|
40
|
-
|
|
45
|
+
var _onUploadingListRef$c;
|
|
46
|
+
(_onUploadingListRef$c = onUploadingListRef.current) === null || _onUploadingListRef$c === void 0 || _onUploadingListRef$c.call(onUploadingListRef, deepCopy(uploadingList));
|
|
41
47
|
setLastUploadList(deepCopy(uploadingList));
|
|
42
48
|
}
|
|
43
|
-
}, [uploadingList, lastUploadList,
|
|
49
|
+
}, [uploadingList, lastUploadList, onUploadingListRef]);
|
|
44
50
|
|
|
45
51
|
// Cleanup function
|
|
46
52
|
useEffect(function () {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
/**
|
|
3
|
+
* 把一个每次渲染都可能换引用的值(通常是回调 prop)装进一个**引用恒定**的 ref。
|
|
4
|
+
*
|
|
5
|
+
* 用途只有一个:让 effect 不必把回调放进依赖数组。
|
|
6
|
+
* 回调放进依赖数组、effect 体内又调它,是一条会让消费方死循环的写法——
|
|
7
|
+
* 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
|
|
8
|
+
* 每次渲染都是新引用 → effect 重跑 → 调回调 → 父级 setState → 再渲染 →
|
|
9
|
+
* 又是新引用 …… 直到 React 抛 `Maximum update depth exceeded`。
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const onDoneRef = useLatestRef(onDone);
|
|
13
|
+
* useEffect(() => {
|
|
14
|
+
* const t = setTimeout(() => onDoneRef.current?.(), 1000);
|
|
15
|
+
* return () => clearTimeout(t);
|
|
16
|
+
* }, [onDoneRef]); // 只在挂载时跑一次,且永远调到最新的 onDone
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* 注意:渲染期间就地赋值。React 官方对 ref 的约束是「不要在渲染中**读**可变值」,
|
|
20
|
+
* 写入最新的 props 是 useEffectEvent 落地前的通行做法(ahooks 的 `useLatest`、
|
|
21
|
+
* React 文档里的 `useEvent` polyfill 都是这么写的)。
|
|
22
|
+
*/
|
|
23
|
+
export declare function useLatestRef<T>(value: T): import("react").MutableRefObject<T>;
|
|
24
|
+
export default useLatestRef;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 把一个每次渲染都可能换引用的值(通常是回调 prop)装进一个**引用恒定**的 ref。
|
|
5
|
+
*
|
|
6
|
+
* 用途只有一个:让 effect 不必把回调放进依赖数组。
|
|
7
|
+
* 回调放进依赖数组、effect 体内又调它,是一条会让消费方死循环的写法——
|
|
8
|
+
* 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
|
|
9
|
+
* 每次渲染都是新引用 → effect 重跑 → 调回调 → 父级 setState → 再渲染 →
|
|
10
|
+
* 又是新引用 …… 直到 React 抛 `Maximum update depth exceeded`。
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const onDoneRef = useLatestRef(onDone);
|
|
14
|
+
* useEffect(() => {
|
|
15
|
+
* const t = setTimeout(() => onDoneRef.current?.(), 1000);
|
|
16
|
+
* return () => clearTimeout(t);
|
|
17
|
+
* }, [onDoneRef]); // 只在挂载时跑一次,且永远调到最新的 onDone
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* 注意:渲染期间就地赋值。React 官方对 ref 的约束是「不要在渲染中**读**可变值」,
|
|
21
|
+
* 写入最新的 props 是 useEffectEvent 落地前的通行做法(ahooks 的 `useLatest`、
|
|
22
|
+
* React 文档里的 `useEvent` polyfill 都是这么写的)。
|
|
23
|
+
*/
|
|
24
|
+
export function useLatestRef(value) {
|
|
25
|
+
var ref = useRef(value);
|
|
26
|
+
ref.current = value;
|
|
27
|
+
return ref;
|
|
28
|
+
}
|
|
29
|
+
export default useLatestRef;
|
|
@@ -8,6 +8,7 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
8
8
|
var _indexModule = _interopRequireDefault(require("../index.module.scss"));
|
|
9
9
|
var echarts = _interopRequireWildcard(require("echarts"));
|
|
10
10
|
var _bubble = require("../_utils/bubble");
|
|
11
|
+
var _useLatestRef = require("../../../hooks/useLatestRef");
|
|
11
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
13
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
14
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -254,7 +255,10 @@ const Bubble = props => {
|
|
|
254
255
|
chartInstanceRef.current.setOption(chartOption, true);
|
|
255
256
|
}
|
|
256
257
|
}, [chartOption, data]);
|
|
258
|
+
const onChartRef = (0, _useLatestRef.useLatestRef)(onChart);
|
|
257
259
|
|
|
260
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
261
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
258
262
|
// Initialize the chart
|
|
259
263
|
(0, _react.useEffect)(() => {
|
|
260
264
|
if (!chartRef.current) return;
|
|
@@ -267,7 +271,7 @@ const Bubble = props => {
|
|
|
267
271
|
chart = echarts.init(chartRef.current);
|
|
268
272
|
chartInstanceRef.current = chart;
|
|
269
273
|
// Call the onChart callback on first initialization
|
|
270
|
-
|
|
274
|
+
onChartRef.current?.(chart);
|
|
271
275
|
}
|
|
272
276
|
|
|
273
277
|
// Resize first so echarts picks up the correct container size, then apply the option
|
|
@@ -289,7 +293,7 @@ const Bubble = props => {
|
|
|
289
293
|
chartInstanceRef.current?.off("click", onClick);
|
|
290
294
|
}
|
|
291
295
|
};
|
|
292
|
-
}, [chartOption, containerSize.width, containerSize.height, handleResize,
|
|
296
|
+
}, [chartOption, containerSize.width, containerSize.height, handleResize, onChartRef, onClick]);
|
|
293
297
|
|
|
294
298
|
// Dispose the chart instance when the component unmounts
|
|
295
299
|
(0, _react.useEffect)(() => {
|
|
@@ -8,6 +8,7 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
8
8
|
var _useContainerReady = _interopRequireDefault(require("../_hooks/useContainerReady"));
|
|
9
9
|
var _indexModule = _interopRequireDefault(require("../index.module.scss"));
|
|
10
10
|
var echarts = _interopRequireWildcard(require("echarts"));
|
|
11
|
+
var _useLatestRef = require("../../../hooks/useLatestRef");
|
|
11
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
13
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
14
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -41,7 +42,10 @@ const Common = props => {
|
|
|
41
42
|
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
42
43
|
chartInstanceRef.current?.resize();
|
|
43
44
|
}, []);
|
|
45
|
+
const onChartRef = (0, _useLatestRef.useLatestRef)(onChart);
|
|
44
46
|
|
|
47
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
48
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
45
49
|
// Initialize the chart
|
|
46
50
|
(0, _react.useEffect)(() => {
|
|
47
51
|
if (!chartRef.current || !containerReady) return;
|
|
@@ -57,7 +61,7 @@ const Common = props => {
|
|
|
57
61
|
chart.setOption(chartOption, true);
|
|
58
62
|
|
|
59
63
|
// Chart-ready callback (can be used for legend auto-scroll, etc.)
|
|
60
|
-
|
|
64
|
+
onChartRef.current?.(chart);
|
|
61
65
|
|
|
62
66
|
// Add resize listener
|
|
63
67
|
window.addEventListener("resize", handleResize);
|
|
@@ -72,7 +76,7 @@ const Common = props => {
|
|
|
72
76
|
return () => {
|
|
73
77
|
window.removeEventListener("resize", handleResize);
|
|
74
78
|
};
|
|
75
|
-
}, [chartOption, handleResize,
|
|
79
|
+
}, [chartOption, handleResize, onChartRef, containerReady]);
|
|
76
80
|
|
|
77
81
|
// Clean up resources on component unmount
|
|
78
82
|
(0, _react.useEffect)(() => {
|
|
@@ -11,6 +11,7 @@ var echarts = _interopRequireWildcard(require("echarts"));
|
|
|
11
11
|
var _heatmap = require("../_utils/heatmap");
|
|
12
12
|
var _chartTheme = require("../_utils/chartTheme");
|
|
13
13
|
var _useIsDark = _interopRequireDefault(require("../../../hooks/useIsDark"));
|
|
14
|
+
var _useLatestRef = require("../../../hooks/useLatestRef");
|
|
14
15
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
16
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
17
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -191,7 +192,10 @@ const Heatmap = props => {
|
|
|
191
192
|
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
192
193
|
chartInstanceRef.current?.resize();
|
|
193
194
|
}, []);
|
|
195
|
+
const onChartRef = (0, _useLatestRef.useLatestRef)(onChart);
|
|
194
196
|
|
|
197
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
198
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
195
199
|
// Initialize the chart
|
|
196
200
|
(0, _react.useEffect)(() => {
|
|
197
201
|
if (!chartRef.current || !containerReady) return;
|
|
@@ -202,7 +206,7 @@ const Heatmap = props => {
|
|
|
202
206
|
chart = echarts.init(chartRef.current);
|
|
203
207
|
chartInstanceRef.current = chart;
|
|
204
208
|
// Call the onChart callback on first initialization
|
|
205
|
-
|
|
209
|
+
onChartRef.current?.(chart);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
// Apply the option
|
|
@@ -229,7 +233,7 @@ const Heatmap = props => {
|
|
|
229
233
|
chartInstanceRef.current?.off("click", onClick);
|
|
230
234
|
}
|
|
231
235
|
};
|
|
232
|
-
}, [chartOption, handleResize,
|
|
236
|
+
}, [chartOption, handleResize, onChartRef, onClick, containerReady]);
|
|
233
237
|
|
|
234
238
|
// Clean up resources when the component unmounts
|
|
235
239
|
(0, _react.useEffect)(() => {
|
|
@@ -11,6 +11,7 @@ var _react = _interopRequireWildcard(require("react"));
|
|
|
11
11
|
var _hooks = require("./_hooks");
|
|
12
12
|
var _option = require("./_utils/option");
|
|
13
13
|
var _indexModule = _interopRequireDefault(require("../../index.module.scss"));
|
|
14
|
+
var _useLatestRef = require("../../../../hooks/useLatestRef");
|
|
14
15
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
15
16
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
17
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
@@ -84,13 +85,17 @@ const ChartPie3D = props => {
|
|
|
84
85
|
yOffset,
|
|
85
86
|
autoRotate
|
|
86
87
|
});
|
|
88
|
+
const onChartRef = (0, _useLatestRef.useLatestRef)(onChart);
|
|
89
|
+
|
|
90
|
+
// 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
|
|
91
|
+
// 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
|
|
87
92
|
(0, _react.useEffect)(() => {
|
|
88
93
|
if (!chartRef.current || !containerReady) return;
|
|
89
94
|
let chart = chartInstanceRef.current;
|
|
90
95
|
if (!chart) {
|
|
91
96
|
chart = echarts.init(chartRef.current);
|
|
92
97
|
chartInstanceRef.current = chart;
|
|
93
|
-
|
|
98
|
+
onChartRef.current?.(chart);
|
|
94
99
|
}
|
|
95
100
|
chart.setOption(chartOption, true);
|
|
96
101
|
optionRef.current = chartOption;
|
|
@@ -114,7 +119,7 @@ const ChartPie3D = props => {
|
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
};
|
|
117
|
-
}, [chartOption, handleResize, handleMouseOver, handleGlobalOut,
|
|
122
|
+
}, [chartOption, handleResize, handleMouseOver, handleGlobalOut, onChartRef, onClick, containerReady]);
|
|
118
123
|
(0, _react.useEffect)(() => {
|
|
119
124
|
return () => {
|
|
120
125
|
if (resizeObserverRef.current) {
|