Skip to content

Commit da6ec68

Browse files
committed
feat: add a callback for verification failure.
1 parent 987d715 commit da6ec68

File tree

4 files changed

+69
-7
lines changed

4 files changed

+69
-7
lines changed

playground/src/app/(demo)/form/modules/Default.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ const Default = () => {
2626

2727
return (
2828
<Form
29-
className="w-[480px] space-y-4"
29+
className="w-[480px] max-sm:w-full space-y-4"
3030
form={form}
3131
onFinish={values => {
3232
console.log(values);
3333
}}
34+
onFinishFailed={errors => {
35+
console.log(errors);
36+
}}
3437
>
3538
<FormField<FormValues>
3639
label="Username"

primitives/filed-form /src/FieldContext.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,17 @@ export interface OperationOptions<Values = any> {
4747
validateFields: (...names: AllPaths<Values>[]) => Promise<boolean>;
4848
}
4949

50+
export interface ValidateErrorEntity<Values = any> {
51+
// 便于 UI 直接滚动
52+
errorCount: number; // 可选:按 _pruneForSubmit 过滤后的值(给埋点/回放)
53+
errorFields: Meta<string, any>[]; // 逐字段列表(用于滚动到第一个错误、逐项显示)
54+
errorMap: Record<string, string[]>; // 同上:警告
55+
firstErrorName?: string; // 全量当前值(未裁剪)
56+
submittedAt: number;
57+
values: Values; // 快速索引(表头红点、侧边分组统计)
58+
warningMap: Record<string, string[]>;
59+
}
60+
5061
export interface RegisterCallbackOptions<Values = any> {
5162
onFieldsChange?: (
5263
changedFields: Meta<AllPaths<Values>, PathToDeepType<Values, AllPaths<Values>>>[],
@@ -55,6 +66,8 @@ export interface RegisterCallbackOptions<Values = any> {
5566

5667
onFinish?: (values: Values) => void;
5768

69+
onFinishFailed?: (errorInfo: ValidateErrorEntity<Values>) => void;
70+
5871
onValuesChange?: (changedValues: Partial<Values>, values: Values) => void;
5972
}
6073

primitives/filed-form /src/Form.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ const Form = <Values=any, As extends ElementType = 'form'>(props: FormProps<Valu
3838
initialValues,
3939
onFieldsChange,
4040
onFinish,
41+
onFinishFailed,
4142
onValuesChange,
4243
preserve = true,
4344
validateMessages,
@@ -75,6 +76,7 @@ const Form = <Values=any, As extends ElementType = 'form'>(props: FormProps<Valu
7576
setCallbacks({
7677
onFieldsChange,
7778
onFinish,
79+
onFinishFailed,
7880
onValuesChange
7981
});
8082

primitives/filed-form /src/createStore.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -640,15 +640,30 @@ class FormStore {
640640
// 并发淘汰:如果 token 改变,放弃落盘
641641
if (this._validateToken.get(key) !== token) return false;
642642

643-
// 写回(空即删)
644-
errors.length ? this._errors.set(key, errors) : this._errors.delete(key);
643+
// === 对比新旧,只有变化才落盘 & 通知 ===
644+
const prevErrors = this._errors.get(key) || [];
645+
const prevWarns = this._warnings.get(key) || [];
645646

646-
warns.length ? this._warnings.set(key, warns) : this._warnings.delete(key);
647+
const errorsChanged = !isEqual(prevErrors, errors);
648+
const warnsChanged = !isEqual(prevWarns, warns);
649+
650+
if (errorsChanged) {
651+
errors.length ? this._errors.set(key, errors) : this._errors.delete(key);
652+
}
653+
if (warnsChanged) {
654+
warns.length ? this._warnings.set(key, warns) : this._warnings.delete(key);
655+
}
647656

648657
this._validating.delete(key);
649658

650-
this.enqueueNotify([key], ChangeTag.Validated | ChangeTag.Errors | ChangeTag.Warnings);
659+
// 只在对应位变化时,才把位并进掩码
660+
let mask: ChangeMask = ChangeTag.Validated;
661+
if (errorsChanged) mask |= ChangeTag.Errors;
662+
if (warnsChanged) mask |= ChangeTag.Warnings;
663+
664+
this.enqueueNotify([key], mask);
651665

666+
// 是否需要始终回调由你的业务决定;如也想避免无变化触发,可加条件判断
652667
this.triggerOnFieldsChange([key]);
653668

654669
return errors.length === 0;
@@ -786,6 +801,8 @@ class FormStore {
786801
}
787802
};
788803

804+
// ===== Submit =====
805+
789806
usePreSubmit(fn: (values: Store) => Store) {
790807
this._preSubmit.push(fn);
791808
}
@@ -814,11 +831,38 @@ class FormStore {
814831
return walk(values, []) ?? {};
815832
}
816833

817-
// ===== Submit =====
834+
private buildFailedPayload() {
835+
const errorMap = Object.fromEntries(this._errors);
836+
const warningMap = Object.fromEntries(this._warnings);
837+
838+
const errorFields = Array.from(this._errors.entries()).map(([name, errors]) => ({
839+
errors,
840+
name,
841+
touched: this._touched.has(name),
842+
validating: this._validating.has(name),
843+
value: this.getFieldValue(name),
844+
warnings: this._warnings.get(name) || []
845+
}));
846+
847+
// 注意:Map 的迭代顺序是插入顺序;如果你想“第一个错误字段”根据 DOM 顺序,
848+
// 可以在这里按你的字段注册顺序(_fieldEntities)排序一次。
849+
const firstErrorName = errorFields[0]?.name;
850+
851+
return {
852+
errorCount: errorFields.length,
853+
errorFields,
854+
errorMap,
855+
firstErrorName,
856+
submittedAt: Date.now(),
857+
values: this._store,
858+
warningMap
859+
};
860+
}
818861
private submit = () => {
819862
this.validateFields().then(ok => {
863+
console.log('submit', ok);
820864
if (ok) this._callbacks.onFinish?.(this._store);
821-
else this._callbacks.onFinishFailed?.(this._store);
865+
else this._callbacks.onFinishFailed?.(this.buildFailedPayload());
822866
});
823867
};
824868

0 commit comments

Comments
 (0)