Skip to content

Commit acd9ffd

Browse files
committed
chore: all batch
1 parent afbd660 commit acd9ffd

File tree

4 files changed

+48
-14
lines changed

4 files changed

+48
-14
lines changed

src/BatchUpdate.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import * as React from 'react';
22

3+
export type BatchTask = (key: string, callback: VoidFunction) => void;
4+
35
export interface BatchUpdateRef {
4-
batch: (key: string, callback: VoidFunction) => void;
6+
batch: BatchTask;
57
}
68

79
const BatchUpdate = React.forwardRef<BatchUpdateRef>((_, ref) => {

src/Form.tsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +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';
17+
import BatchUpdate, { BatchTask, type BatchUpdateRef } from './BatchUpdate';
1818

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

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

64-
const batchUpdateRef = React.useRef<BatchUpdateRef>(null);
65-
6664
// We customize handle event since Context will makes all the consumer re-render:
6765
// https://reactjs.org/docs/context.html#contextprovider
6866
const [formInstance] = useForm(form);
@@ -122,9 +120,42 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
122120
mountRef.current = true;
123121
}
124122

123+
// ======================== Batch Update ========================
124+
// zombieJ:
125+
// To avoid Form self re-render,
126+
// We create a sub component `BatchUpdate` to handle batch update logic.
127+
// When the call with do not change immediate, we will batch the update
128+
// and flush it in `useLayoutEffect` for next tick.
129+
125130
// Set batch update ref
126-
setBatchUpdate(batchUpdateRef);
131+
const batchUpdateRef = React.useRef<BatchUpdateRef>(null);
132+
const batchUpdateTasksRef = React.useRef<[key: string, fn: VoidFunction][]>([]);
133+
134+
const tryFlushBatch = () => {
135+
if (batchUpdateRef.current) {
136+
batchUpdateTasksRef.current.forEach(([key, fn]) => {
137+
batchUpdateRef.current.batch(key, fn);
138+
});
139+
batchUpdateTasksRef.current = [];
140+
}
141+
};
142+
143+
// Ref update
144+
const setBatchUpdateRef = React.useCallback((batchUpdate: BatchUpdateRef | null) => {
145+
batchUpdateRef.current = batchUpdate;
146+
tryFlushBatch();
147+
}, []);
148+
149+
// Task list
150+
151+
const batchUpdate: BatchTask = (key, callback) => {
152+
batchUpdateTasksRef.current.push([key, callback]);
153+
tryFlushBatch();
154+
};
155+
156+
setBatchUpdate(batchUpdate);
127157

158+
// ========================== Unmount ===========================
128159
React.useEffect(
129160
() => () => destroyForm(clearOnDestroy),
130161
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -153,6 +184,7 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
153184
prevFieldsRef.current = fields;
154185
}, [fields, formInstance]);
155186

187+
// =========================== Render ===========================
156188
const formContextValue = React.useMemo(
157189
() => ({
158190
...(formInstance as InternalFormInstance),
@@ -164,7 +196,7 @@ const Form: React.ForwardRefRenderFunction<FormRef, FormProps> = (
164196
const wrapperNode = (
165197
<ListContext.Provider value={null}>
166198
<FieldContext.Provider value={formContextValue}>{childrenNode}</FieldContext.Provider>
167-
<BatchUpdate ref={batchUpdateRef} />
199+
<BatchUpdate ref={setBatchUpdateRef} />
168200
</ListContext.Provider>
169201
);
170202

src/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +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';
4+
import type { BatchTask } from './BatchUpdate';
55

66
export type InternalNamePath = (string | number)[];
77
export type NamePath<T = any> = DeepNamePath<T>;
@@ -234,7 +234,7 @@ export interface InternalHooks {
234234
setValidateMessages: (validateMessages: ValidateMessages) => void;
235235
setPreserve: (preserve?: boolean) => void;
236236
getInitialValue: (namePath: InternalNamePath) => StoreValue;
237-
setBatchUpdate: (batchUpdate: React.RefObject<BatchUpdateRef>) => void;
237+
setBatchUpdate: (fn: BatchTask) => void;
238238
}
239239

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

src/useForm.ts

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

4343
type InvalidateFieldEntity = { INVALIDATE_NAME_PATH: InternalNamePath };
4444

@@ -226,15 +226,15 @@ export class FormStore {
226226
};
227227

228228
// ============================= Batch ============================
229-
private batchUpdateRef: React.RefObject<BatchUpdateRef>;
229+
private batchUpdate: BatchTask;
230230

231-
private setBatchUpdate = (batchUpdate: React.RefObject<BatchUpdateRef>) => {
232-
this.batchUpdateRef = batchUpdate;
231+
private setBatchUpdate = (batchUpdate: BatchTask) => {
232+
this.batchUpdate = batchUpdate;
233233
};
234234

235235
// Batch call the task, only last will be called
236236
private batch = (key: string, callback: VoidFunction) => {
237-
this.batchUpdateRef.current?.batch(key, callback);
237+
this.batchUpdate(key, callback);
238238
};
239239

240240
// ========================== Dev Warning =========================
@@ -665,7 +665,7 @@ export class FormStore {
665665
private registerField = (entity: FieldEntity) => {
666666
this.fieldEntities.push(entity);
667667
const namePath = entity.getNamePath();
668-
this.notifyWatch([namePath]);
668+
this.batchNotifyWatch(namePath);
669669

670670
// Set initial values
671671
if (entity.props.initialValue !== undefined) {

0 commit comments

Comments
 (0)