Skip to content

Commit a86047d

Browse files
committed
refactor(form): refactor core, merge type definitions, optimize validation logic, and remove redundant code
1 parent c52194e commit a86047d

File tree

11 files changed

+289
-197
lines changed

11 files changed

+289
-197
lines changed

primitives/filed-form /src/form-core/createStore.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66

77
import { assign, isArray, isEqual, isNil, toArray } from 'skyroc-utils';
88

9-
import type { FieldEntity } from '../types/field';
10-
import type { Callbacks, Store, StoreValue } from '../types/formStore';
11-
import type { Meta } from '../types/shared-types';
129
import { get } from '../utils/get';
1310
import { set, unset } from '../utils/set';
1411
import type { NamePath, PathTuple } from '../utils/util';
@@ -17,7 +14,8 @@ import { anyOn, collectDeepKeys, isOn, isUnderPrefix, keyOfName, microtask } fro
1714
import { type ChangeMask, ChangeTag } from './event';
1815
import type { Action, ArrayOpArgs, Middleware, ValidateFieldsOptions } from './middleware';
1916
import { compose } from './middleware';
20-
import type { ValidateMessages } from './types';
17+
import type { Callbacks, FieldEntity, Meta, Store, StoreValue } from './types';
18+
import type { ValidateMessages } from './validate';
2119
import { runRulesWithMode } from './validation';
2220
import type { Rule, ValidateOptions } from './validation';
2321

@@ -34,6 +32,9 @@ export type ArrayField = {
3432
keys: number[];
3533
};
3634

35+
/**
36+
* Checks if a validation rule should be triggered based on the provided trigger
37+
*/
3738
const matchTrigger = (rule: Rule, trig?: string | string[]) => {
3839
const list = toArray(rule.validateTrigger);
3940

@@ -44,12 +45,18 @@ const matchTrigger = (rule: Rule, trig?: string | string[]) => {
4445
return trigList.some(t => list.includes(t));
4546
};
4647

48+
/**
49+
* Extracts values from a Map based on provided field names
50+
*/
4751
function getValueByNames<T>(source: Map<string, T>, names?: NamePath[]): Record<string, T> {
4852
const keys = names?.length ? names.map(keyOfName) : Array.from(source.keys());
4953

5054
return Object.fromEntries(keys.map(k => [k, source.get(k)!]));
5155
}
5256

57+
/**
58+
* Checks if any field in the provided names has a flag set in the bucket
59+
*/
5360
function getFlag(bucket: Set<string>, names?: NamePath[]) {
5461
if (!names || names.length === 0) {
5562
return bucket.size > 0;
@@ -58,6 +65,9 @@ function getFlag(bucket: Set<string>, names?: NamePath[]) {
5865
return anyOn(bucket, names);
5966
}
6067

68+
/**
69+
* Moves an array element from one index to another
70+
*/
6171
function move<T>(arr: T[], from: number, to: number): T[] {
6272
const clone = arr.slice();
6373
const item = clone.splice(from, 1)[0];
@@ -210,9 +220,7 @@ class FormStore {
210220
private rebindMiddlewares = () => {
211221
const api = {
212222
dispatch: (a: Action) => this.dispatch(a),
213-
// eslint-disable-next-line sort/object-properties
214-
getState: () => this._store,
215-
getFields: this.getFields
223+
getState: () => this._store
216224
};
217225

218226
const chain = this._middlewares.map(m => m(api));
@@ -245,11 +253,11 @@ class FormStore {
245253
this.arrayOp(a.name, a.args);
246254
break;
247255
case 'setExternalErrors': {
248-
const { entries } = a as any;
256+
const { entries } = a;
249257

250258
this.transaction(() => {
251259
if (entries.length === 0) {
252-
// ✅ 空数组代表全通过,清空所有错误
260+
// ✅ Empty array means all validation passed, clear all errors
253261
this._errors.clear();
254262
this.enqueueNotify(
255263
Array.from(this._fieldEntities, e => e.name as string),
@@ -983,6 +991,9 @@ class FormStore {
983991

984992
// ===== Array Operation =====
985993

994+
/**
995+
* Gets or creates an array key manager for tracking array field keys
996+
*/
986997
private getArrayKeyManager = (name: string) => {
987998
let mgr = this.arrayKeyMap.get(name);
988999
if (!mgr) {
@@ -992,6 +1003,9 @@ class FormStore {
9921003
return mgr;
9931004
};
9941005

1006+
/**
1007+
* Performs array operations on form fields (insert, remove, move, swap, replace)
1008+
*/
9951009
private arrayOp(name: NamePath, args: ArrayOpArgs): void {
9961010
const arr = this.getFieldValue(name);
9971011
if (!Array.isArray(arr)) return;
@@ -1077,6 +1091,10 @@ class FormStore {
10771091
};
10781092

10791093
// ===== FieldChange =====
1094+
/**
1095+
* Triggers the onFieldsChange callback for specified fields
1096+
* @param nameList - Array of field names that have changed
1097+
*/
10801098
triggerOnFieldsChange = (nameList: NamePath[]) => {
10811099
if (this._callbacks?.onFieldsChange) {
10821100
const fields = this.getFields();
@@ -1091,10 +1109,16 @@ class FormStore {
10911109

10921110
// ===== Submit =====
10931111

1112+
/**
1113+
* Registers a pre-submit transform function
1114+
*/
10941115
usePreSubmit(fn: (values: Store) => Store) {
10951116
this._preSubmit.push(fn);
10961117
}
10971118

1119+
/**
1120+
* Removes disabled and hidden fields from values before submission
1121+
*/
10981122
private _pruneForSubmit(values: Store): Store {
10991123
const disabled = Array.from(this._disabledKeys);
11001124
const hidden = Array.from(this._hiddenKeys);
@@ -1119,6 +1143,10 @@ class FormStore {
11191143
return walk(values, []) ?? {};
11201144
}
11211145

1146+
/**
1147+
* Builds the payload for failed form submission
1148+
* @returns Object containing error information and form state
1149+
*/
11221150
private buildFailedPayload = () => {
11231151
const errorMap = Object.fromEntries(this._errors);
11241152
const warningMap = Object.fromEntries(this._warnings);
@@ -1148,13 +1176,20 @@ class FormStore {
11481176
};
11491177
};
11501178

1179+
/**
1180+
* Submits the form after validation
1181+
* Calls onFinish if validation passes, onFinishFailed if it fails
1182+
*/
11511183
private submit = () => {
11521184
this.validateFields().then(ok => {
11531185
if (ok) this._callbacks.onFinish?.(this._store);
11541186
else this._callbacks.onFinishFailed?.(this.buildFailedPayload());
11551187
});
11561188
};
11571189

1190+
/**
1191+
* Destroys the form and cleans up all state
1192+
*/
11581193
private destroyForm = (clearOnDestroy?: boolean) => {
11591194
if (clearOnDestroy) {
11601195
// destroy form reset store

primitives/filed-form /src/form-core/event.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,95 @@
11
/* eslint-disable @typescript-eslint/prefer-literal-enum-member */
22
/* eslint-disable no-bitwise */
33

4+
/**
5+
* Form field change event system
6+
* Uses bitwise flags to efficiently track and combine different types of field changes
7+
*/
8+
9+
/**
10+
* Enumeration of change tags using bitwise flags
11+
* Each tag represents a different type of field change that can be tracked
12+
*/
413
export enum ChangeTag {
14+
/** Field value has changed */
515
Value = 0b000001,
16+
/** Field is currently being validated */
617
Validating = 0b000010,
18+
/** Field validation errors have changed */
719
Errors = 0b000100,
20+
/** Field validation warnings have changed */
821
Warnings = 0b001000,
22+
/** Field has been touched by user interaction */
923
Touched = 0b010000,
24+
/** Field value differs from initial value */
1025
Dirty = 0b100000,
26+
/** Field validation has completed */
1127
Validated = 0b1000000,
28+
/** Field has been reset */
1229
Reset = 0b10000000,
30+
/** Combination of all validation status flags */
1331
Status = Errors | Warnings | Validated | Validating,
32+
/** All possible change flags */
1433
All = 0x7fffffff
1534
}
1635

36+
/**
37+
* Type representing a bitmask of change flags
38+
* Used to efficiently combine and check multiple change types
39+
*/
1740
export type ChangeMask = number;
1841

42+
/**
43+
* Checks if a change mask contains a specific tag
44+
* @param mask - The change mask to check
45+
* @param tag - The tag to look for
46+
* @returns True if the mask contains the tag
47+
*/
1948
export const hasTag = (mask: ChangeMask, tag: ChangeTag) => (mask & tag) !== 0;
49+
50+
/**
51+
* Adds one or more tags to a change mask
52+
*/
2053
export const addTag = (mask: ChangeMask, ...tags: ChangeTag[]) => tags.reduce((m, t) => m | t, mask);
54+
55+
/**
56+
* Removes one or more tags from a change mask
57+
*/
2158
export const delTag = (mask: ChangeMask, ...tags: ChangeTag[]) => tags.reduce((m, t) => m & ~t, mask);
2259

60+
/**
61+
* Options for configuring subscription masks
62+
* Used to specify which types of changes to listen for
63+
*/
2364
export interface SubscribeMaskOptions {
65+
/** Subscribe to all change types */
2466
all?: boolean;
67+
/** Subscribe to dirty state changes */
2568
dirty?: boolean;
69+
/** Subscribe to validation error changes */
2670
errors?: boolean;
71+
/** Subscribe to field reset events */
2772
reset?: boolean;
73+
/** Subscribe to touched state changes */
2874
touched?: boolean;
75+
/** Subscribe to validation completion events */
2976
validated?: boolean;
77+
/** Subscribe to validation status changes */
3078
validating?: boolean;
79+
/** Subscribe to value changes */
3180
value?: boolean;
81+
/** Subscribe to validation warning changes */
3282
warnings?: boolean;
3383
}
3484

85+
/**
86+
* Converts subscription options to a change mask
87+
*/
3588
export const toMask = (opt: SubscribeMaskOptions = {}): ChangeMask => {
89+
// If 'all' is specified, return mask for all changes
3690
if (opt.all) return ChangeTag.All;
3791

92+
// Build array of selected tags
3893
const tags: ChangeTag[] = [];
3994
if (opt.value) tags.push(ChangeTag.Value);
4095
if (opt.errors) tags.push(ChangeTag.Errors);
@@ -45,5 +100,6 @@ export const toMask = (opt: SubscribeMaskOptions = {}): ChangeMask => {
45100
if (opt.dirty) tags.push(ChangeTag.Dirty);
46101
if (opt.reset) tags.push(ChangeTag.Reset);
47102

103+
// Combine all selected tags into a single mask
48104
return addTag(0, ...tags);
49105
};

primitives/filed-form /src/form-core/middleware.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,91 @@
1+
/**
2+
* Form middleware system for action processing and validation
3+
* Provides type-safe action definitions and middleware composition utilities
4+
*/
5+
16
import type { AllPathsKeys, PathToDeepType } from 'skyroc-type-utils';
27

3-
import type { MetaShapeFromPaths } from '../react/hooks/FieldContext';
48
import type { NamePath } from '../utils/util';
59

610
import type { ValidateOptions } from './validation';
711

12+
/**
13+
* Extended validation options for validating multiple fields
14+
*/
815
export interface ValidateFieldsOptions extends ValidateOptions {
16+
/** Only validate fields that have been modified (dirty) */
917
dirty?: boolean;
1018
}
1119

20+
/**
21+
* Available array operations for form field arrays
22+
*/
1223
export type ArrayOp = 'insert' | 'move' | 'remove' | 'replace' | 'swap';
1324

25+
/**
26+
* Arguments for different array operations
27+
* Each operation type has its own specific argument structure
28+
*/
1429
export type ArrayOpArgs =
15-
| { index: number; item: any; op: 'insert' }
16-
| { index: number; op: 'remove' }
17-
| { from: number; op: 'move'; to: number }
18-
| { from: number; op: 'swap'; to: number }
19-
| { index: number; item: any; op: 'replace' };
30+
| { index: number; item: any; op: 'insert' } // Insert item at index
31+
| { index: number; op: 'remove' } // Remove item at index
32+
| { from: number; op: 'move'; to: number } // Move item from one index to another
33+
| { from: number; op: 'swap'; to: number } // Swap items at two indices
34+
| { index: number; item: any; op: 'replace' }; // Replace item at index
2035

36+
/**
37+
* Utility type to extract arguments for a specific array operation
38+
*/
2139
export type ArgsOf<T extends ArrayOp> = Extract<ArrayOpArgs, { op: T }>;
2240

41+
/**
42+
* Action type for array operations on form fields
43+
*/
2344
export type ArrayOpAction = { args: ArgsOf<ArrayOp>; name: NamePath; type: 'arrayOp' };
2445

46+
/**
47+
* Union type representing all possible form actions
48+
* Each action type corresponds to a specific form operation
49+
*/
2550
export type Action<Values = any, T extends AllPathsKeys<Values> = AllPathsKeys<Values>> =
26-
| { name: T; type: 'setFieldValue'; validate?: boolean; value: PathToDeepType<Values, T> }
27-
| { type: 'setFieldsValue'; validate?: boolean; values: Values }
28-
| { names?: T[]; type: 'reset' }
29-
| { name: T; opts?: ValidateOptions; type: 'validateField' }
30-
| { name?: T[]; opts?: ValidateFieldsOptions; type: 'validateFields' }
31-
| { entries: Array<[T, string[]]>; type: 'setExternalErrors' }
32-
| ArrayOpAction;
51+
| { name: T; type: 'setFieldValue'; validate?: boolean; value: PathToDeepType<Values, T> } // Set single field value
52+
| { type: 'setFieldsValue'; validate?: boolean; values: Values } // Set multiple field values
53+
| { names?: T[]; type: 'reset' } // Reset fields to initial values
54+
| { name: T; opts?: ValidateOptions; type: 'validateField' } // Validate single field
55+
| { name?: T[]; opts?: ValidateFieldsOptions; type: 'validateFields' } // Validate multiple fields
56+
| { entries: Array<[T, string[]]>; type: 'setExternalErrors' } // Set external validation errors
57+
| ArrayOpAction; // Array operations
3358

59+
/**
60+
* Context object provided to middleware functions
61+
* Contains methods to interact with the form state and dispatch actions
62+
*/
3463
export type MiddlewareCtx<Values, T extends AllPathsKeys<Values> = AllPathsKeys<Values>> = {
64+
/** Dispatch an action to the form store */
3565
dispatch(a: Action<Values, T>): void;
36-
getFields(): MetaShapeFromPaths<Values, T[]>;
66+
/** Get current form state values */
3767
getState(): Values;
3868
};
3969

70+
/**
71+
* Middleware function type for form action processing
72+
* Follows the standard middleware pattern: (ctx) => (next) => (action) => void
73+
* Allows intercepting and modifying form actions before they reach the store
74+
*/
4075
export type Middleware<Values = any, T extends AllPathsKeys<Values> = AllPathsKeys<Values>> = (
4176
ctx: MiddlewareCtx<Values, T>
4277
) => (next: (a: Action<Values, T>) => void) => (a: Action<Values, T>) => void;
4378

79+
/**
80+
* Composes multiple functions into a single function
81+
* Used to combine multiple middleware functions into a single middleware chain
82+
*/
4483
export function compose(...fns: ((...args: any[]) => any)[]) {
84+
// No functions provided, return identity function
4585
if (fns.length === 0) return (arg: any) => arg;
86+
// Single function, return it directly
4687
if (fns.length === 1) return fns[0];
88+
// Multiple functions, compose them right-to-left
4789
return fns.reduce(
4890
(a, b) =>
4991
(...args: any[]) =>

0 commit comments

Comments
 (0)