@zat-design/sisyphus-react 4.5.8-beta.6 → 4.5.9-beta.1

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.
@@ -12,7 +12,7 @@ import classNames from 'classnames';
12
12
  import { compatStartTransition } from "../../../utils";
13
13
  import valueTypeMap from "../../../ProForm/utils/valueType";
14
14
  import transformMap from "../../utils/transform";
15
- import { getNamePath, difference, getDisabled, resolveChangedFields, syncGroupCombinedValues } from "../../utils/tools";
15
+ import { getNamePath, difference, getDisabled, isRecordShallowEqual, resolveChangedFields, syncGroupCombinedValues } from "../../utils/tools";
16
16
  import * as componentMap from "../../../ProForm/components";
17
17
  import { useProConfig } from "../../../ProConfigProvider";
18
18
  import Container from "../../../ProForm/components/Container";
@@ -26,7 +26,7 @@ import { getDefaultProps } from "../../utils/getDefaultProps";
26
26
  import ListChangedWrapper from "./ListChangedWrapper";
27
27
  import useShouldUpdateForTable from "../../utils/useShouldUpdateForTable";
28
28
  import { OMIT_FORM_ITEM_AND_DOM_KEYS, arePropsEqual, hasRowFunctionDependency, mergeOnFieldChangeRow } from "./tools";
29
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
29
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
30
30
  const RenderField = ({
31
31
  text: value,
32
32
  record,
@@ -90,6 +90,8 @@ const RenderField = ({
90
90
 
91
91
  // type类型 首字母转大写
92
92
  type = type?.replace?.(type[0], type[0].toUpperCase()) || 'Input';
93
+ const fallbackType = type;
94
+ const hasCustomComponent = 'component' in column || 'editRender' in column;
93
95
 
94
96
  // 自定义组件时 防止默认值影像后续判断
95
97
  if ('component' in column) {
@@ -104,6 +106,11 @@ const RenderField = ({
104
106
  const namePath = useMemo(() => getNamePath(name, virtualKey), [namePathKey, name, virtualKey]);
105
107
  const rowNamePath = useMemo(() => [...namePath, index], [namePath, index]);
106
108
  const rowData = form.getFieldValue(rowNamePath) || record || {};
109
+
110
+ // Form.Item shouldUpdate 可能在 RenderField 外层未重渲染时多次执行 render props。
111
+ // 缓存本次外层渲染期间的最新行计算结果,避免同一行版本重复调用动态配置函数。
112
+ const shouldUpdateStateCacheRef = useRef(undefined);
113
+ shouldUpdateStateCacheRef.current = undefined;
107
114
  let currentValue = dataIndex ? rowData?.[dataIndex] : null;
108
115
 
109
116
  // 构建新的参数格式(与 ProForm 保持一致)
@@ -171,7 +178,7 @@ const RenderField = ({
171
178
 
172
179
  // component 处理 - 优先使用 hook 返回的值
173
180
  if (_isFunction(lastComponent)) {
174
- lastComponent = dynamicProps.component ?? lastComponent(rowData, reactiveParams);
181
+ lastComponent = dynamicProps.componentResolved ? dynamicProps.component : lastComponent(rowData, reactiveParams);
175
182
  }
176
183
 
177
184
  // 更新 lastEditRender 为处理后的 lastComponent
@@ -193,6 +200,23 @@ const RenderField = ({
193
200
 
194
201
  // 多态:可能是内置组件类型、自定义 component,或已渲染的 ReactElement(lastEditRender),保留显式 any
195
202
  let TargetComponent;
203
+ const fallbackTargetComponent = typeof fallbackType === 'string' ? componentMap[fallbackType] : undefined;
204
+ const invalidComponentWarnedRef = useRef(false);
205
+ const resolveEditTargetComponent = candidate => {
206
+ if ( /*#__PURE__*/React.isValidElement(candidate)) {
207
+ invalidComponentWarnedRef.current = false;
208
+ return candidate;
209
+ }
210
+ if (hasCustomComponent && !invalidComponentWarnedRef.current && process.env.NODE_ENV !== 'production') {
211
+ const columnName = dataIndex ?? column?.name ?? title ?? originTitle ?? 'unknown';
212
+ console.warn('[ProEditTable] component 必须返回合法的 ReactElement,已回退到内置组件。', {
213
+ column: Array.isArray(columnName) ? columnName.join('.') : String(columnName),
214
+ received: candidate
215
+ });
216
+ invalidComponentWarnedRef.current = true;
217
+ }
218
+ return fallbackTargetComponent;
219
+ };
196
220
 
197
221
  // 将Hooks调用移到组件顶层
198
222
  const proConfig = useProConfig();
@@ -315,20 +339,9 @@ const RenderField = ({
315
339
  ...valueTypeTransform()
316
340
  }, form, names, namesStr, type);
317
341
  }, [names, baseName, index, dataIndex, lastFormItemProps, type]);
318
- if (!lastEditRender && typeof type === 'string') {
319
- // componentMap 为内置组件命名空间,按字符串 type 动态取用,保留显式 any 索引
320
- TargetComponent = componentMap[type] ?? /*#__PURE__*/_jsx(_Fragment, {});
321
- }
322
- if (isEditable && isEditing) {
323
- // lastEditRender 已经是处理后的值(通过 dynamicProps.component 或直接计算)
324
- // 如果原本是函数,此时 lastEditRender 已经是执行后的 ReactNode
325
- // 如果原本是 ReactElement,lastEditRender 就是 ReactElement
326
- if ( /*#__PURE__*/React.isValidElement(lastEditRender)) {
327
- TargetComponent = lastEditRender;
328
- } else if (lastEditRender) {
329
- // 其他情况(可能是字符串或其他类型的 ReactNode)
330
- TargetComponent = lastEditRender;
331
- }
342
+ TargetComponent = fallbackTargetComponent;
343
+ if (isEditable && isEditing && hasCustomComponent) {
344
+ TargetComponent = resolveEditTargetComponent(lastEditRender);
332
345
  }
333
346
 
334
347
  // 查看模式
@@ -663,45 +676,48 @@ const RenderField = ({
663
676
  namePath: rowNamePath
664
677
  };
665
678
  const latestRowParams = [latestRowData, latestReactiveParams];
679
+ const cachedState = shouldUpdateStateCacheRef.current;
680
+ if (cachedState && isRecordShallowEqual(cachedState.rowSnapshot, latestRowData)) {
681
+ return cachedState.result;
682
+ }
666
683
 
667
- // 关键修改:shouldUpdate 模式下,跳过缓存,直接重新计算所有响应式属性
668
- let latestFieldProps = fieldProps || {};
669
- if (_isFunction(fieldProps)) {
684
+ // 初次 render props 会与外层 RenderField 使用同一行数据,直接复用 hook 已计算结果。
685
+ const canReuseCurrentState = isRecordShallowEqual(latestRowData, dynamicProps.rowSnapshot);
686
+ let latestFieldProps = canReuseCurrentState ? lastFieldProps : fieldProps || {};
687
+ if (!canReuseCurrentState && _isFunction(fieldProps)) {
670
688
  latestFieldProps = fieldProps(latestRowData, latestReactiveParams);
671
689
  }
672
-
673
- // 重新计算 required(用于可能的后续逻辑)
674
- let latestRequired = required;
675
- if (_isFunction(required)) {
690
+ let latestRequired = canReuseCurrentState ? lastRequired : required;
691
+ if (!canReuseCurrentState && _isFunction(required)) {
676
692
  latestRequired = required(latestRowData, latestReactiveParams);
677
693
  }
678
-
679
- // 重新计算 rules(用于可能的后续逻辑)
680
- let latestRules = rules;
681
- if (_isFunction(rules)) {
694
+ let latestRules = canReuseCurrentState ? lastRules : rules;
695
+ if (!canReuseCurrentState && _isFunction(rules)) {
682
696
  latestRules = rules(latestRowData, latestReactiveParams);
683
697
  }
684
-
685
- // 重新计算 desensitization
686
- let latestDesensitization = desensitization || [];
687
- if (_isFunction(desensitization)) {
698
+ let latestDesensitization = canReuseCurrentState ? lastDesensitization : desensitization || [];
699
+ if (!canReuseCurrentState && _isFunction(desensitization)) {
688
700
  latestDesensitization = desensitization(latestRowData, latestReactiveParams);
689
701
  }
690
-
691
- // 重新计算 component
692
- let latestComponent = component || editRender;
693
- if (_isFunction(latestComponent)) {
702
+ let latestComponent = canReuseCurrentState ? lastComponent : component || editRender;
703
+ if (!canReuseCurrentState && _isFunction(latestComponent)) {
694
704
  latestComponent = latestComponent(latestRowData, latestReactiveParams);
695
705
  }
696
-
697
- // 重新计算 isEditable(使用原始的 column.isEditable,而不是处理后的 isEditable)
698
- let latestIsEditable = column.isEditable ?? true;
699
- if (_isFunction(column.isEditable)) {
706
+ let latestIsEditable = canReuseCurrentState ? isEditable : column.isEditable ?? true;
707
+ if (!canReuseCurrentState && _isFunction(column.isEditable)) {
700
708
  latestIsEditable = column.isEditable(latestRowData, latestReactiveParams);
701
709
  }
702
-
703
- // 重新计算 disabled
704
- const latestDisabled = getDisabled({
710
+ let latestValueType = canReuseCurrentState ? lastValueType : valueType;
711
+ if (!canReuseCurrentState && _isFunction(valueType)) {
712
+ const latestCurrentValue = dataIndex ? latestRowData?.[dataIndex] : null;
713
+ latestValueType = valueType(latestCurrentValue, latestRowData, {
714
+ index,
715
+ form,
716
+ namePath: rowNamePath,
717
+ name: column?.name
718
+ });
719
+ }
720
+ const latestDisabled = canReuseCurrentState ? lastDisabled : getDisabled({
705
721
  globalControl: otherProps?.globalControl,
706
722
  formDisabled: otherProps?.formDisabled,
707
723
  column,
@@ -721,15 +737,7 @@ const RenderField = ({
721
737
  onChange: handleChange,
722
738
  onBlur: handleBlur
723
739
  };
724
-
725
- // ⭐ 关键修改:在 shouldUpdate 模式下,需要重新设置 TargetComponent
726
- // 因为 component 函数可能返回不同的结果
727
- let latestTargetComponent = TargetComponent;
728
-
729
- // 首先检查是否有内置type
730
- if (!latestComponent && typeof type === 'string') {
731
- latestTargetComponent = componentMap[type] ?? /*#__PURE__*/_jsx(_Fragment, {});
732
- }
740
+ let latestTargetComponent = canReuseCurrentState ? TargetComponent : fallbackTargetComponent;
733
741
 
734
742
  // 然后处理自定义 component
735
743
  const latestIsView = !latestIsEditable || record?.['is-view'] || config.isView || virtualKey && !isEditing || getDisabled({
@@ -741,16 +749,12 @@ const RenderField = ({
741
749
  params: latestRowParams,
742
750
  rowDisabled: rowDisabled || 'empty'
743
751
  });
744
- if (latestIsEditable && isEditing) {
745
- if ( /*#__PURE__*/React.isValidElement(latestComponent)) {
746
- latestTargetComponent = latestComponent;
747
- } else if (latestComponent) {
748
- latestTargetComponent = latestComponent;
749
- }
752
+ if (!canReuseCurrentState && latestIsEditable && isEditing && hasCustomComponent) {
753
+ latestTargetComponent = resolveEditTargetComponent(latestComponent);
750
754
  }
751
755
 
752
756
  // 查看模式
753
- if (latestIsView) {
757
+ if (!canReuseCurrentState && latestIsView) {
754
758
  if (typeof viewRender === 'function') {
755
759
  const latestCurrentValue = dataIndex ? latestRowData?.[dataIndex] : value;
756
760
  const View = viewRender(latestCurrentValue, latestRowData || {}, options);
@@ -769,7 +773,8 @@ const RenderField = ({
769
773
  ...latestComponentProps,
770
774
  otherProps: {
771
775
  ...latestComponentProps.otherProps,
772
- isView: latestIsView
776
+ isView: latestIsView,
777
+ valueType: latestValueType
773
778
  }
774
779
  };
775
780
 
@@ -788,7 +793,7 @@ const RenderField = ({
788
793
  };
789
794
  }
790
795
  }
791
- return {
796
+ const result = {
792
797
  finalComponentProps: latestComponentProps,
793
798
  targetComponent: latestTargetComponent,
794
799
  latestIsView,
@@ -796,6 +801,13 @@ const RenderField = ({
796
801
  effectiveRules: latestRules,
797
802
  effectiveFieldProps: latestFieldProps
798
803
  };
804
+ shouldUpdateStateCacheRef.current = {
805
+ rowSnapshot: {
806
+ ...latestRowData
807
+ },
808
+ result
809
+ };
810
+ return result;
799
811
  };
800
812
 
801
813
  // 根据 latestIsView 和最新 required/rules 构建正确的 Form.Item props
@@ -925,7 +937,7 @@ const RenderField = ({
925
937
  }) : /*#__PURE__*/_jsx(Container, {
926
938
  viewEmpty: viewEmpty
927
939
  });
928
- if (type === 'FormList') {
940
+ if (type === 'FormList' && !hasCustomComponent && TargetComponent) {
929
941
  FormItem = /*#__PURE__*/_jsx(Form.List, {
930
942
  name: formNamePath ? cellName.slice(formNamePath?.length - 1) : cellName,
931
943
  ..._omit(activeFinalFormItemProps, ['render', 'key', 'width', 'hiddenNames', 'name']),
@@ -17,8 +17,24 @@ import Empty from "../assets/empty.png";
17
17
  import locale, { formatMessage } from "../locale";
18
18
  import { BaseTable, DraggableTable } from "./components/RcTable";
19
19
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
20
+ const EMPTY_VALUE = [];
21
+ const EMPTY_ACTION_PROPS = [];
22
+ const EMPTY_TOOLBAR_PROPS = [];
23
+
24
+ /** 保留浅层语义相同对象的引用,避免 rest props 每次分配新对象导致整表重算。 */
25
+ const useShallowStableObject = value => {
26
+ const valueRef = useRef(value);
27
+ const previous = valueRef.current;
28
+ const previousKeys = Object.keys(previous);
29
+ const nextKeys = Object.keys(value);
30
+ const changed = previousKeys.length !== nextKeys.length || nextKeys.some(key => !Object.is(previous[key], value[key]));
31
+ if (changed) {
32
+ valueRef.current = value;
33
+ }
34
+ return valueRef.current;
35
+ };
20
36
  const ProEditTable = ({
21
- value = [],
37
+ value = EMPTY_VALUE,
22
38
  onChange,
23
39
  onDrag,
24
40
  onDragEnd,
@@ -32,8 +48,8 @@ const ProEditTable = ({
32
48
  insertType = 'after',
33
49
  emptyBtnText = locale.ProEditTable.clickAdd,
34
50
  actionWidth,
35
- actionProps = [],
36
- toolbarProps = [],
51
+ actionProps = EMPTY_ACTION_PROPS,
52
+ toolbarProps = EMPTY_TOOLBAR_PROPS,
37
53
  toolbarSticky,
38
54
  rowSelection,
39
55
  onlyOneLineMsg = locale.ProEditTable.onlyOneLineMsg,
@@ -53,13 +69,14 @@ const ProEditTable = ({
53
69
  diffConfig,
54
70
  ...resetProps
55
71
  }, ref) => {
72
+ const stableResetProps = useShallowStableObject(resetProps);
56
73
  // 上下文form
57
74
  const contentForm = Form.useFormInstance();
58
75
  const formFieldProps = ProForm.useFieldProps() || {};
59
76
  const affixRef = useRef(null);
60
- const form = contentForm || formFieldProps?.form || resetProps?.form;
61
- let name = formFieldProps?.name || resetProps?.name || resetProps?.id?.split?.('_');
62
- const shouldUpdateDebounce = formFieldProps?.shouldUpdateDebounce ?? resetProps?.shouldUpdateDebounce ?? 200;
77
+ const form = contentForm || formFieldProps?.form || stableResetProps?.form;
78
+ let name = formFieldProps?.name || stableResetProps?.name || stableResetProps?.id?.split?.('_');
79
+ const shouldUpdateDebounce = formFieldProps?.shouldUpdateDebounce ?? stableResetProps?.shouldUpdateDebounce ?? 200;
63
80
  const {
64
81
  isView = false,
65
82
  viewEmpty = '-',
@@ -71,6 +88,9 @@ const ProEditTable = ({
71
88
  name = [...namePath, ...name.slice(1)];
72
89
  disabled = formFieldProps?.disabled || disabled; // formFieldProps?.disabled可能是函数??
73
90
  }
91
+ const nameSignature = Array.isArray(name) ? name.join('\u0001') : String(name);
92
+ // ProForm 上下文可能每次渲染都生成新的 namePath 数组,按路径语义稳定引用。
93
+ name = useMemo(() => name, [nameSignature]);
74
94
  const tableRef = useRef(null);
75
95
  // 虚拟表格错误存储:脱离 antd Field 生命周期持久化真实报错文案,
76
96
  // 解决滚动卸载后报错丢失(报错常驻)。按表格实例隔离。
@@ -95,6 +115,15 @@ const ProEditTable = ({
95
115
  const requiredAlign = useMemo(() => {
96
116
  return themeConfig?.data?.zauiFormAlign ?? configRequiredAlign ?? 'left';
97
117
  }, [themeConfig?.data?.zauiFormAlign, configRequiredAlign]);
118
+ const renderOtherProps = useMemo(() => ({
119
+ globalControl: stableResetProps?.otherProps?.globalControl,
120
+ formDisabled: stableResetProps?.otherProps?.formDisabled,
121
+ desensitizationKey: stableResetProps?.otherProps?.desensitizationKey
122
+ }), [stableResetProps?.otherProps?.desensitizationKey, stableResetProps?.otherProps?.formDisabled, stableResetProps?.otherProps?.globalControl]);
123
+ const mergedDiffConfig = useMemo(() => ({
124
+ ...stableResetProps?.otherProps?.diffConfig,
125
+ ...diffConfig
126
+ }), [diffConfig, stableResetProps?.otherProps?.diffConfig]);
98
127
  const [state, setState] = useSetState({
99
128
  forceUpdate: {},
100
129
  // 表格内部强制刷新开关
@@ -127,7 +156,7 @@ const ProEditTable = ({
127
156
  pageSize: paginationConfig?.pageSize ?? page.pageSize
128
157
  }), [page.pageNum, page.pageSize, paginationConfig?.current, paginationConfig?.pageSize]);
129
158
  const mergedSelectedRowKeys = rowSelection?.selectedRowKeys ?? selectedRowKeys;
130
- const virtualRowName = getNamePath(_isArray(name) ? name : [name], virtualKey);
159
+ const virtualRowName = useMemo(() => getNamePath(_isArray(name) ? name : [name], virtualKey), [name, virtualKey]);
131
160
 
132
161
  // 样式处理
133
162
  const _className = classnames({
@@ -150,7 +179,7 @@ const ProEditTable = ({
150
179
  const originalValues = useMemo(() => {
151
180
  let nextOriginalValues;
152
181
  // 原始值也需要设置对应的rowkey,后续比对通过rowkey对比
153
- const originalArr = _get(resetProps?.originalValues, name);
182
+ const originalArr = _get(stableResetProps?.originalValues, name);
154
183
  const isOriginalAllHasKey = originalArr ? originalArr?.every?.(item => item.rowKey) : true;
155
184
  const isNeedCoverOriginal = originalArr?.some?.(item => item.rowKey !== getRowKey(item));
156
185
  // 初始化默认生成row-key
@@ -164,14 +193,14 @@ const ProEditTable = ({
164
193
  _set(nextOriginalValues, name, originalArr);
165
194
  }
166
195
  return nextOriginalValues;
167
- }, [resetProps?.originalValues]);
196
+ }, [getRowKey, name, stableResetProps?.originalValues]);
168
197
  const getIsNew = useCallback(record => {
169
198
  const originalArr = _get(originalValues, name);
170
199
  if (!originalArr?.length) {
171
200
  return false;
172
201
  }
173
202
  return !originalArr?.find?.(item => getRowKey(item) === getRowKey(record));
174
- }, [originalValues, name]);
203
+ }, [getRowKey, originalValues, name]);
175
204
 
176
205
  // 分页变更
177
206
  const handlePageChange = useCallback(async (current, pageSize) => {
@@ -230,27 +259,27 @@ const ProEditTable = ({
230
259
  prefixCls,
231
260
  rowDisabled,
232
261
  actionDirection,
233
- diffConfig: {
234
- ...resetProps?.otherProps?.diffConfig,
235
- ...diffConfig
236
- },
262
+ diffConfig: mergedDiffConfig,
237
263
  shouldUpdateDebounce,
238
264
  getIsNew,
239
265
  handlePageChange,
240
- ...resetProps
266
+ validateTrigger: stableResetProps?.validateTrigger,
267
+ validateMessages: stableResetProps?.validateMessages,
268
+ virtual: stableResetProps?.virtual,
269
+ otherProps: renderOtherProps
241
270
  }), [actionDirection, actionProps, actionWidth, columns, deletePoConfirmMsg, diffConfig, editingKeys, emptyBtnText, form,
242
271
  // forceUpdate 不应该作为依赖项,因为它是触发刷新的信号,而非用于比较的数据
243
- getIsNew, handlePageChange, insertType, isView, max, mode, mulDeletePoConfirmMsg, name, namePath, onlyOneLineMsg, originalValues, prefixCls, requiredAlign, resetProps, rowDisabled, mergedSelectedRowKeys, selectedRows, shouldUpdateDebounce, tableRef, toolbarProps, value?.length, viewEmpty, virtualKey, resolvedPage]);
272
+ getIsNew, handlePageChange, insertType, isView, max, mergedDiffConfig, mode, mulDeletePoConfirmMsg, name, namePath, onlyOneLineMsg, originalValues, prefixCls, requiredAlign, renderOtherProps, rowDisabled, mergedSelectedRowKeys, selectedRows, shouldUpdateDebounce, stableResetProps?.validateMessages, stableResetProps?.validateTrigger, stableResetProps?.virtual, tableRef, toolbarProps, value?.length, viewEmpty, virtualKey, resolvedPage]);
244
273
 
245
274
  // 编辑行设置下样式
246
- const _rowClassName = record => {
275
+ const _rowClassName = useCallback(record => {
247
276
  const isEdit = !virtualKey || editingKeys.includes(record.rowKey);
248
- const className = isEdit ? 'is-editing' : '';
277
+ const rowClassName = isEdit ? 'is-editing' : '';
249
278
  if (getIsNew(record)) {
250
- return `is-new-row ${className}`;
279
+ return `is-new-row ${rowClassName}`;
251
280
  }
252
- return className;
253
- };
281
+ return rowClassName;
282
+ }, [editingKeys, getIsNew, virtualKey]);
254
283
 
255
284
  // 判断是否禁止添加、批量删除
256
285
  const isForbiddenBtn = useCallback(type => {
@@ -274,7 +303,7 @@ const ProEditTable = ({
274
303
  const isHideCheckBox = !rowSelection && isForbiddenBtn('mulDelete');
275
304
 
276
305
  // 复选框
277
- const _rowSelection = {
306
+ const _rowSelection = useMemo(() => ({
278
307
  fixed: true,
279
308
  columnWidth: 48,
280
309
  getCheckboxProps: record => {
@@ -296,7 +325,7 @@ const ProEditTable = ({
296
325
  });
297
326
  rowSelection?.onChange?.(nextSelectedRowKeys, nextSelectedRows, info);
298
327
  }
299
- };
328
+ }), [isHideCheckBox, mergedSelectedRowKeys, rowDisabled, rowSelection, setState]);
300
329
 
301
330
  // 空列表状态
302
331
  const emptyDom = useMemo(() => () => /*#__PURE__*/_jsxs("div", {
@@ -321,7 +350,7 @@ const ProEditTable = ({
321
350
  * 渲染表格选择器
322
351
  * @returns rowSelection
323
352
  */
324
- const renderRowSelection = () => {
353
+ const renderRowSelection = useCallback(() => {
325
354
  if (disabled || isView) {
326
355
  return null;
327
356
  }
@@ -332,10 +361,10 @@ const ProEditTable = ({
332
361
  return _rowSelection;
333
362
  }
334
363
  return null;
335
- };
364
+ }, [_rowSelection, disabled, draggable, isHideCheckBox, isView]);
336
365
  const _columns = useMemo(() => {
337
366
  return transformColumns(columns, config, cellCachesRef.current);
338
- }, [disabled, forceUpdate, columns, page, actionProps, editingKeys, config]);
367
+ }, [columns, config, forceUpdate]);
339
368
  const initDataSource = () => {
340
369
  // 检查每一项是否有 rowKey 或通过 rowKey 字段获取的 key
341
370
  const isAllHasKey = value?.every?.(item => item.rowKey || typeof rowKey === 'string' && item[rowKey]);
@@ -359,7 +388,7 @@ const ProEditTable = ({
359
388
  useEffect(() => {
360
389
  initDataSource();
361
390
  }, [value, originalValues]);
362
- useImperativeHandle(resetProps?.ref || ref, () => {
391
+ useImperativeHandle(stableResetProps?.ref || ref, () => {
363
392
  return {
364
393
  getInternalState: () => state,
365
394
  setInternalState: setState
@@ -377,11 +406,11 @@ const ProEditTable = ({
377
406
  }
378
407
  }, [toolbarSticky]);
379
408
  const _toolbarSticky = _isBoolean(toolbarSticky) ? {} : toolbarSticky;
380
- const _summary = typeof summary === 'object' ? data => /*#__PURE__*/_jsx(Summary, {
409
+ const _summary = useMemo(() => typeof summary === 'object' ? data => /*#__PURE__*/_jsx(Summary, {
381
410
  data: data,
382
411
  summary: summary,
383
412
  rowSelection: renderRowSelection()
384
- }) : summary;
413
+ }) : summary, [renderRowSelection, summary]);
385
414
  const TableComponent = draggable ? DraggableTable : BaseTable;
386
415
 
387
416
  // 给每个 record 附加编辑状态标识,让 shouldCellUpdate 能检测到状态变化
@@ -435,17 +464,18 @@ const ProEditTable = ({
435
464
  }) => rest);
436
465
  onChange?.(cleanValue);
437
466
  }, [onChange]);
467
+ const draggableProps = useMemo(() => ({
468
+ onChange: draggable ? handleDragChange : onChange,
469
+ onDrag,
470
+ onDragEnd,
471
+ draggable
472
+ }), [draggable, handleDragChange, onChange, onDrag, onDragEnd]);
438
473
  return /*#__PURE__*/_jsxs(_Fragment, {
439
474
  children: [/*#__PURE__*/_jsx(ConfigProvider, {
440
475
  renderEmpty: value?.length ? undefined : emptyDom,
441
476
  children: /*#__PURE__*/_jsx(TableComponent, {
442
477
  ...resetProps,
443
- draggableProps: {
444
- onChange: draggable ? handleDragChange : onChange,
445
- onDrag,
446
- onDragEnd,
447
- draggable
448
- },
478
+ draggableProps: draggableProps,
449
479
  tableProps: tableProps
450
480
  })
451
481
  }), !isView && value?.length ? toolbarSticky ? /*#__PURE__*/_jsx(Affix, {
@@ -485,7 +515,7 @@ const ProEditTable = ({
485
515
  virtualKey: virtualKey,
486
516
  editingKeys: editingKeys,
487
517
  onlyOneLineMsg: onlyOneLineMsg,
488
- virtual: resetProps?.virtual,
518
+ virtual: stableResetProps?.virtual,
489
519
  columns: columns,
490
520
  config: config,
491
521
  errorStore: errorStoreRef.current
@@ -1,5 +1,5 @@
1
1
  import { AffixProps, ModalFuncProps } from 'antd';
2
- import { Key, ReactNode } from 'react';
2
+ import { Key, ReactElement, ReactNode } from 'react';
3
3
  import { DragEndEvent } from '@dnd-kit/core';
4
4
  import type { ButtonProps } from 'antd/es/button';
5
5
  import type { TooltipProps } from 'antd/es/tooltip';
@@ -134,7 +134,7 @@ export type viewRenderFn<T = any> = (text?: any, record?: T, options?: OptionsPr
134
134
  * 组件渲染函数类型(与 ReactiveFunction 保持一致)
135
135
  * @template T 记录类型
136
136
  */
137
- export type ComponentRenderFn<T = any> = ReactiveFunction<T, string | number | ReactNode | void>;
137
+ export type ComponentRenderFn<T = any> = ReactiveFunction<T, ReactElement>;
138
138
  /**
139
139
  * 表格列属性接口
140
140
  * @template Values 值类型
@@ -157,8 +157,8 @@ export interface ProColumnsProps<Values = any, T = any> extends Omit<FormItemPro
157
157
  tooltip?: string | ({
158
158
  icon?: string | ReactNode;
159
159
  } & TooltipProps);
160
- /** 组件 */
161
- component?: string | number | ReactNode | ComponentRenderFn<T>;
160
+ /** 自定义组件,或返回合法 ReactElement 的响应式渲染函数 */
161
+ component?: ReactElement | ComponentRenderFn<T>;
162
162
  /** 视图渲染 */
163
163
  viewRender?: string | number | ReactNode | viewRenderFn<T>;
164
164
  /** 隐藏的字段名 */
@@ -11,6 +11,10 @@ interface Result {
11
11
  desensitization: any;
12
12
  valueType: any;
13
13
  component: any;
14
+ /** 动态 component 是否已经执行过,用于区分“返回 undefined”和“尚未计算”。 */
15
+ componentResolved: boolean;
16
+ /** 当前缓存结果对应的行浅快照,供 RenderField 判断是否可安全复用。 */
17
+ rowSnapshot: Record<string, any> | undefined;
14
18
  }
15
19
  declare const useShouldUpdateForTable: (props: UseShouldUpdateForTableProps) => Result;
16
20
  export default useShouldUpdateForTable;
@@ -3,6 +3,7 @@ import _isEqualWith from "lodash/isEqualWith";
3
3
  import _isFunction from "lodash/isFunction";
4
4
  import { useRef, useState, useEffect } from 'react';
5
5
  import { customEqualForFunction } from "../../utils";
6
+ import { isRecordShallowEqual } from "./tools";
6
7
  const useShouldUpdateForTable = props => {
7
8
  const {
8
9
  rowParams,
@@ -18,6 +19,8 @@ const useShouldUpdateForTable = props => {
18
19
  const desensitizationRef = useRef(undefined);
19
20
  const valueTypeRef = useRef(undefined);
20
21
  const componentRef = useRef(undefined);
22
+ const componentResolvedRef = useRef(false);
23
+ const resolvedRowSnapshotRef = useRef(undefined);
21
24
  const [, reRender] = useState({});
22
25
  const debouncedUpdateRef = useRef(null);
23
26
  const pendingParamsRef = useRef(null);
@@ -99,6 +102,7 @@ const useShouldUpdateForTable = props => {
99
102
  const componentOrEditRender = column.component || column.editRender;
100
103
  if (_isFunction(componentOrEditRender)) {
101
104
  const newComponent = componentOrEditRender(values, reactiveParams);
105
+ componentResolvedRef.current = true;
102
106
  // 对于 ReactNode 类型的返回值,使用深度比较
103
107
  if (!_isEqualWith(componentRef.current, newComponent, customEqualForFunction)) {
104
108
  componentRef.current = newComponent;
@@ -107,6 +111,9 @@ const useShouldUpdateForTable = props => {
107
111
  } else {
108
112
  componentRef.current = componentOrEditRender;
109
113
  }
114
+ resolvedRowSnapshotRef.current = values && typeof values === 'object' ? {
115
+ ...values
116
+ } : values;
110
117
  if (hasChange) {
111
118
  reRender({});
112
119
  }
@@ -136,12 +143,15 @@ const useShouldUpdateForTable = props => {
136
143
  const fieldPropsFactoryChanged = prevFieldPropsFactoryRef.current !== currentFieldPropsFactory;
137
144
  prevFieldPropsFactoryRef.current = currentFieldPropsFactory;
138
145
 
139
- // 行数据引用 + 行索引均不变时跳过深比较(虚拟滚动常见路径)
146
+ // 上一次保存的是浅快照,可同时覆盖引用稳定和 onFieldChange 原地修改两种路径。
140
147
  const prevRowParams = prevRowParamsRef.current;
141
- const rowDataUnchanged = isInitializedRef.current && prevRowParams?.[0] === rowParams[0] && prevRowParams?.[1]?.index === rowParams[1]?.index;
148
+ const rowDataUnchanged = isInitializedRef.current && isRecordShallowEqual(prevRowParams?.[0], rowParams[0]) && prevRowParams?.[1]?.index === rowParams[1]?.index;
142
149
  const shouldProcess = !isInitializedRef.current || fieldPropsFactoryChanged || !rowDataUnchanged && !_isEqualWith(prevRowParams, rowParams, customEqualForFunction);
143
150
  if (shouldProcess) {
144
- prevRowParamsRef.current = rowParams;
151
+ const currentRowData = rowParams[0];
152
+ prevRowParamsRef.current = [currentRowData && typeof currentRowData === 'object' ? {
153
+ ...currentRowData
154
+ } : currentRowData, rowParams[1]];
145
155
  isInitializedRef.current = true;
146
156
  if (fieldPropsFactoryChanged) {
147
157
  // 外部依赖变化会生成新的 fieldProps 函数,需要立即替换旧闭包缓存
@@ -162,7 +172,9 @@ const useShouldUpdateForTable = props => {
162
172
  fieldProps: fieldPropsRef.current ?? column.fieldProps,
163
173
  desensitization: desensitizationRef.current ?? column.desensitization,
164
174
  valueType: valueTypeRef.current ?? column.valueType,
165
- component: componentRef.current ?? (column.component || column.editRender)
175
+ component: componentResolvedRef.current ? componentRef.current : column.component || column.editRender,
176
+ componentResolved: componentResolvedRef.current,
177
+ rowSnapshot: resolvedRowSnapshotRef.current
166
178
  };
167
179
  };
168
180
  export default useShouldUpdateForTable;
@@ -81,9 +81,11 @@ export function useRequestList(service, options, useRequestOptions) {
81
81
  });
82
82
  }, [onPageChange]);
83
83
  const onSearch = useCallback(values => {
84
+ if (page.pageNum !== 1) {
85
+ // 查询或重置都从第一页开始,保留当前每页条数
86
+ onChange(1, page.pageSize);
87
+ }
84
88
  if (!_isEqual(searchValues, values)) {
85
- // 保留当前页码和每页条数
86
- onChange(page.pageNum, page.pageSize);
87
89
  setSearchValues(values);
88
90
  }
89
91
  }, [onChange, searchValues, page]);
@@ -51,6 +51,7 @@ export function TabsHeader({
51
51
  const tabKeys = (tabsItems ?? []).map(item => item.key);
52
52
  return /*#__PURE__*/_jsx("div", {
53
53
  className: "pro-layout-tabs-header",
54
+ "data-pro-layout-tabs-measure": "true",
54
55
  children: /*#__PURE__*/_jsx(Tabs, {
55
56
  activeKey: activeKey,
56
57
  onChange: onTabChange,
@@ -171,12 +171,11 @@
171
171
  padding: 4px 0 5px !important;
172
172
  margin-right: 8px !important;
173
173
  margin-left: 0;
174
- background: #e7e8ee;
174
+ background: #F5F5F5;
175
175
  transition: none;
176
176
  border-color: #e7e8ee;
177
177
  &.@{ant-prefix}-tabs-tab-active {
178
178
  background: #ffffff;
179
- border-top: 2px solid var(--zaui-brand, #006aff);
180
179
  }
181
180
  }
182
181
  .ant-tabs-nav {