Skip to content

Commit bc301aa

Browse files
committed
fix: batch remove laggy
1 parent 6611c31 commit bc301aa

File tree

6 files changed

+121
-1
lines changed

6 files changed

+121
-1
lines changed

docs/demo/debug.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## debug
2+
3+
4+
<code src="../examples/debug.tsx"></code>

docs/examples/debug.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import Form, { Field } from 'rc-field-form';
2+
import React from 'react';
3+
4+
function WatchCom({ name }: { name: number }) {
5+
const data = Form.useWatch(name);
6+
return data;
7+
}
8+
9+
export default function App() {
10+
const [form] = Form.useForm();
11+
const [open, setOpen] = React.useState<boolean>(true);
12+
13+
const data = React.useMemo(() => {
14+
return Array.from({ length: 1 * 500 }).map((_, i) => ({
15+
key: i,
16+
name: `Edward King ${i}`,
17+
age: 32,
18+
address: `London, Park Lane no. ${i}`,
19+
}));
20+
}, []);
21+
22+
return (
23+
<Form form={form}>
24+
<div className="App">
25+
{/* When I made the switch, it was very laggy */}
26+
<button
27+
onClick={() => {
28+
setOpen(!open);
29+
}}
30+
>
31+
Switch
32+
</button>
33+
<WatchCom name={0} />
34+
<WatchCom name={1} />
35+
{open ? (
36+
<div className="flex gap-[5px] flex-wrap">
37+
{data?.map(item => {
38+
return (
39+
<Field key={item.name} name={item.name}>
40+
<input />
41+
</Field>
42+
);
43+
})}
44+
</div>
45+
) : (
46+
<h2>some thing</h2>
47+
)}
48+
</div>
49+
</Form>
50+
);
51+
}

src/BatchUpdate.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react';
2+
3+
export interface BatchUpdateRef {
4+
batch: (key: string, callback: VoidFunction) => void;
5+
}
6+
7+
const BatchUpdate = React.forwardRef<BatchUpdateRef>((_, ref) => {
8+
const [batchInfo, setBatchInfo] = React.useState<Record<string, VoidFunction>>({});
9+
10+
React.useLayoutEffect(() => {
11+
const keys = Object.keys(batchInfo);
12+
if (keys.length) {
13+
keys.forEach(key => {
14+
batchInfo[key]?.();
15+
});
16+
setBatchInfo({});
17+
}
18+
}, [batchInfo]);
19+
20+
React.useImperativeHandle(ref, () => ({
21+
batch: (key, callback) => {
22+
setBatchInfo(ori => ({
23+
...ori,
24+
[key]: callback,
25+
}));
26+
},
27+
}));
28+
29+
return null;
30+
});
31+
32+
export default BatchUpdate;

src/Form.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { FormContextProps } from './FormContext';
1414
import FormContext from './FormContext';
1515
import { isSimilar } from './utils/valueUtil';
1616
import ListContext from './ListContext';
17+
import BatchUpdate, { BatchUpdateRef } from './BatchUpdate';
1718

1819
type BaseFormProps = Omit<React.FormHTMLAttributes<HTMLFormElement>, 'onSubmit' | 'children'>;
1920

@@ -60,6 +61,8 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
6061
const nativeElementRef = React.useRef<HTMLFormElement>(null);
6162
const formContext: FormContextProps = React.useContext(FormContext);
6263

64+
const batchUpdateRef = React.useRef<BatchUpdateRef>(null);
65+
6366
// We customize handle event since Context will makes all the consumer re-render:
6467
// https://reactjs.org/docs/context.html#contextprovider
6568
const [formInstance] = useForm(form);
@@ -70,6 +73,7 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
7073
setValidateMessages,
7174
setPreserve,
7275
destroyForm,
76+
setBatchUpdate,
7377
} = (formInstance as InternalFormInstance).getInternalHooks(HOOK_MARK);
7478

7579
// Pass ref with form instance
@@ -118,6 +122,9 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
118122
mountRef.current = true;
119123
}
120124

125+
// Set batch update ref
126+
setBatchUpdate(batchUpdateRef);
127+
121128
React.useEffect(
122129
() => () => destroyForm(clearOnDestroy),
123130
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -157,6 +164,7 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
157164
const wrapperNode = (
158165
<ListContext.Provider value={null}>
159166
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
167+
<BatchUpdate ref={batchUpdateRef} />
160168
</ListContext.Provider>
161169
);
162170

src/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactElement } from 'react';
22
import type { DeepNamePath } from './namePathType';
33
import type { ReducerAction } from './useForm';
4+
import type { BatchUpdateRef } from './BatchUpdate';
45

56
export type InternalNamePath = (string | number)[];
67
export type NamePath<T = any> = DeepNamePath<T>;
@@ -233,6 +234,7 @@ export interface InternalHooks {
233234
setValidateMessages: (validateMessages: ValidateMessages) => void;
234235
setPreserve: (preserve?: boolean) => void;
235236
getInitialValue: (namePath: InternalNamePath) => StoreValue;
237+
setBatchUpdate: (batchUpdate: React.RefObject<BatchUpdateRef>) => void;
236238
}
237239

238240
/** Only return partial when type is not any */

src/useForm.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
matchNamePath,
3939
setValue,
4040
} from './utils/valueUtil';
41+
import { BatchUpdateRef } from './BatchUpdate';
4142

4243
type InvalidateFieldEntity = { INVALIDATE_NAME_PATH: InternalNamePath };
4344

@@ -119,6 +120,7 @@ export class FormStore {
119120
setPreserve: this.setPreserve,
120121
getInitialValue: this.getInitialValue,
121122
registerWatch: this.registerWatch,
123+
setBatchUpdate: this.setBatchUpdate,
122124
};
123125
}
124126

@@ -214,6 +216,27 @@ export class FormStore {
214216
}
215217
};
216218

219+
private notifyWatchNamePathList: InternalNamePath[] = [];
220+
private batchNotifyWatch = (namePath: InternalNamePath) => {
221+
this.notifyWatchNamePathList.push(namePath);
222+
this.batch('notifyWatch', () => {
223+
this.notifyWatch(this.notifyWatchNamePathList);
224+
this.notifyWatchNamePathList = [];
225+
});
226+
};
227+
228+
// ============================= Batch ============================
229+
private batchUpdateRef: React.RefObject<BatchUpdateRef>;
230+
231+
private setBatchUpdate = (batchUpdate: React.RefObject<BatchUpdateRef>) => {
232+
this.batchUpdateRef = batchUpdate;
233+
};
234+
235+
// Batch call the task, only last will be called
236+
private batch = (key: string, callback: VoidFunction) => {
237+
this.batchUpdateRef.current?.batch(key, callback);
238+
};
239+
217240
// ========================== Dev Warning =========================
218241
private timeoutId: any = null;
219242

@@ -682,7 +705,7 @@ export class FormStore {
682705
}
683706
}
684707

685-
this.notifyWatch([namePath]);
708+
this.batchNotifyWatch(namePath);
686709
};
687710
};
688711

0 commit comments

Comments
 (0)