@tmagic/form 1.8.0-beta.17 → 1.8.0-beta.18
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.
- package/dist/es/utils/form.js +4 -3
- package/dist/es/utils/typeMatch.js +98 -21
- package/dist/tmagic-form.umd.cjs +102 -24
- package/package.json +4 -4
- package/src/utils/form.ts +7 -4
- package/src/utils/typeMatch.ts +135 -31
- package/types/index.d.ts +12 -3
package/dist/es/utils/form.js
CHANGED
|
@@ -20,15 +20,16 @@ var adaptFormValidator = (validator) => {
|
|
|
20
20
|
const callback = (error) => {
|
|
21
21
|
if (settled) return;
|
|
22
22
|
settled = true;
|
|
23
|
-
|
|
23
|
+
const first = Array.isArray(error) ? error[0] : error;
|
|
24
|
+
if (first) resolve({
|
|
24
25
|
result: false,
|
|
25
|
-
message: typeof
|
|
26
|
+
message: typeof first === "string" ? first : first.message
|
|
26
27
|
});
|
|
27
28
|
else resolve(true);
|
|
28
29
|
};
|
|
29
30
|
try {
|
|
30
31
|
const result = validator(void 0, value, callback);
|
|
31
|
-
if (result
|
|
32
|
+
if (result && typeof result.then === "function") Promise.resolve(result).then(() => {
|
|
32
33
|
if (!settled) callback();
|
|
33
34
|
}, (err) => {
|
|
34
35
|
callback(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -361,6 +361,11 @@ var validateBuiltinTypeMatch = (value, fieldType, mForm, props, message) => {
|
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
363
|
};
|
|
364
|
+
/**
|
|
365
|
+
* 校验取值与字段 type 是否匹配;通过返回 undefined,否则返回错误文案。
|
|
366
|
+
*
|
|
367
|
+
* 命中的自定义规则是异步校验器时返回 Promise,内置规则始终同步返回。
|
|
368
|
+
*/
|
|
364
369
|
var validateTypeMatch = (value, mForm, props, message) => {
|
|
365
370
|
if (isEmptyValue(value) || isEmptyArray(value)) return;
|
|
366
371
|
if (!props.config?.name) return;
|
|
@@ -376,34 +381,106 @@ var validateTypeMatch = (value, mForm, props, message) => {
|
|
|
376
381
|
});
|
|
377
382
|
return validateBuiltinTypeMatch(value, fieldType, mForm, props, message);
|
|
378
383
|
};
|
|
384
|
+
var toError = (error) => error instanceof Error ? error : /* @__PURE__ */ new Error(`${error}`);
|
|
379
385
|
var createTypeMatchValidator = (mForm, props, rule) => {
|
|
380
386
|
const originalValidator = typeof rule.validator === "function" ? rule.validator : void 0;
|
|
387
|
+
/**
|
|
388
|
+
* 每次校验取一个自增序号,只有最新一轮的结论会被采用。
|
|
389
|
+
*
|
|
390
|
+
* 旧值的在途校验不能用自己的结论结算:它可能晚于新校验结束,用过期结论覆盖新结论
|
|
391
|
+
* (表单取最后结束的那次结果);也不能在新校验开始时无条件按通过结算,否则
|
|
392
|
+
* `form.validate()` 会对一个还没校验过的值返回成功。因此所有尚未结算的调用都登记在
|
|
393
|
+
* pendingSettlers 中,等最新一轮出结论后用同一个结论一起结算。
|
|
394
|
+
*
|
|
395
|
+
* 不比较取值本身:配了 names 或取值为对象/数组时拿到的是同一个引用,就地修改后比较不出变化。
|
|
396
|
+
*/
|
|
397
|
+
let generation = 0;
|
|
398
|
+
let pendingSettlers = [];
|
|
381
399
|
return (asyncValidatorRule, value, callback, source, options) => {
|
|
382
400
|
const actualValue = props.config?.names ? props.model : value;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
401
|
+
generation += 1;
|
|
402
|
+
const currentGeneration = generation;
|
|
403
|
+
/** 已有更新的一轮校验开始,本轮结论作废 */
|
|
404
|
+
const isStale = () => currentGeneration !== generation;
|
|
405
|
+
pendingSettlers.push((args) => callback(...args));
|
|
406
|
+
/**
|
|
407
|
+
* 结算本轮与被本轮取代的所有在途校验。
|
|
408
|
+
*
|
|
409
|
+
* async-validator 的 callback 不幂等:重复调用会让它的内部计数提前满足、错误信息重复;
|
|
410
|
+
* 原始 validator 写成 async 时很容易既调 callback 又返回 Promise,故所有回调都收敛到这里,
|
|
411
|
+
* 由「取出即出队」保证每个调用的 callback 只会被调用一次。
|
|
412
|
+
*/
|
|
413
|
+
const conclude = (...args) => {
|
|
414
|
+
if (isStale()) return;
|
|
415
|
+
const settlers = pendingSettlers;
|
|
416
|
+
pendingSettlers = [];
|
|
417
|
+
for (const settle of settlers) settle(args);
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* 执行原始 validator 并把结果统一转成 callback。
|
|
421
|
+
*
|
|
422
|
+
* async-validator 只解析同步返回给它的返回值(true / false / Error / 错误数组 / Promise,
|
|
423
|
+
* 抛错转成错误信息),异步路径已脱离它的调用栈,这些约定会失效导致校验永不结束,故复刻一份。
|
|
424
|
+
*/
|
|
425
|
+
const settleWithOriginalValidator = () => {
|
|
426
|
+
if (isStale()) return;
|
|
427
|
+
if (!originalValidator) {
|
|
428
|
+
conclude();
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
let result;
|
|
432
|
+
try {
|
|
433
|
+
result = originalValidator({
|
|
434
|
+
rule: asyncValidatorRule,
|
|
435
|
+
value: actualValue,
|
|
436
|
+
callback: conclude,
|
|
437
|
+
source,
|
|
438
|
+
options
|
|
439
|
+
}, {
|
|
440
|
+
values: mForm?.initValues || {},
|
|
441
|
+
model: props.model,
|
|
442
|
+
parent: mForm?.parentValues || {},
|
|
443
|
+
formValue: mForm?.values || props.model,
|
|
444
|
+
prop: props.prop,
|
|
445
|
+
config: props.config
|
|
446
|
+
}, mForm);
|
|
447
|
+
} catch (err) {
|
|
448
|
+
conclude(toError(err));
|
|
387
449
|
return;
|
|
388
450
|
}
|
|
389
|
-
|
|
390
|
-
|
|
451
|
+
if (isPromise(result)) result.then(() => conclude(), (err) => conclude(toError(err)));
|
|
452
|
+
else if (result === true) conclude();
|
|
453
|
+
else if (result === false) {
|
|
454
|
+
const field = asyncValidatorRule?.fullField || asyncValidatorRule?.field || props.prop;
|
|
455
|
+
conclude(new Error(rule.message || `${field} fails`));
|
|
456
|
+
} else if (result instanceof Error || Array.isArray(result)) conclude(result);
|
|
457
|
+
};
|
|
458
|
+
const skipFailedTypeMatch = (err) => {
|
|
459
|
+
console.error(err);
|
|
460
|
+
settleWithOriginalValidator();
|
|
461
|
+
};
|
|
462
|
+
let error;
|
|
463
|
+
try {
|
|
464
|
+
error = validateTypeMatch(actualValue, mForm, props, rule.message);
|
|
465
|
+
} catch (err) {
|
|
466
|
+
skipFailedTypeMatch(err);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (isPromise(error)) {
|
|
470
|
+
error.then((asyncMessage) => {
|
|
471
|
+
if (asyncMessage) {
|
|
472
|
+
conclude(new Error(asyncMessage));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
settleWithOriginalValidator();
|
|
476
|
+
}, skipFailedTypeMatch);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (error) {
|
|
480
|
+
conclude(new Error(error));
|
|
481
|
+
return;
|
|
391
482
|
}
|
|
392
|
-
|
|
393
|
-
rule: asyncValidatorRule,
|
|
394
|
-
value: actualValue,
|
|
395
|
-
callback,
|
|
396
|
-
source,
|
|
397
|
-
options
|
|
398
|
-
}, {
|
|
399
|
-
values: mForm?.initValues || {},
|
|
400
|
-
model: props.model,
|
|
401
|
-
parent: mForm?.parentValues || {},
|
|
402
|
-
formValue: mForm?.values || props.model,
|
|
403
|
-
prop: props.prop,
|
|
404
|
-
config: props.config
|
|
405
|
-
}, mForm);
|
|
406
|
-
callback();
|
|
483
|
+
settleWithOriginalValidator();
|
|
407
484
|
};
|
|
408
485
|
};
|
|
409
486
|
//#endregion
|
package/dist/tmagic-form.umd.cjs
CHANGED
|
@@ -3088,6 +3088,11 @@
|
|
|
3088
3088
|
return;
|
|
3089
3089
|
}
|
|
3090
3090
|
};
|
|
3091
|
+
/**
|
|
3092
|
+
* 校验取值与字段 type 是否匹配;通过返回 undefined,否则返回错误文案。
|
|
3093
|
+
*
|
|
3094
|
+
* 命中的自定义规则是异步校验器时返回 Promise,内置规则始终同步返回。
|
|
3095
|
+
*/
|
|
3091
3096
|
var validateTypeMatch = (value, mForm, props, message) => {
|
|
3092
3097
|
if (isEmptyValue(value) || isEmptyArray(value)) return;
|
|
3093
3098
|
if (!props.config?.name) return;
|
|
@@ -3103,34 +3108,106 @@
|
|
|
3103
3108
|
});
|
|
3104
3109
|
return validateBuiltinTypeMatch(value, fieldType, mForm, props, message);
|
|
3105
3110
|
};
|
|
3111
|
+
var toError = (error) => error instanceof Error ? error : /* @__PURE__ */ new Error(`${error}`);
|
|
3106
3112
|
var createTypeMatchValidator = (mForm, props, rule) => {
|
|
3107
3113
|
const originalValidator = typeof rule.validator === "function" ? rule.validator : void 0;
|
|
3114
|
+
/**
|
|
3115
|
+
* 每次校验取一个自增序号,只有最新一轮的结论会被采用。
|
|
3116
|
+
*
|
|
3117
|
+
* 旧值的在途校验不能用自己的结论结算:它可能晚于新校验结束,用过期结论覆盖新结论
|
|
3118
|
+
* (表单取最后结束的那次结果);也不能在新校验开始时无条件按通过结算,否则
|
|
3119
|
+
* `form.validate()` 会对一个还没校验过的值返回成功。因此所有尚未结算的调用都登记在
|
|
3120
|
+
* pendingSettlers 中,等最新一轮出结论后用同一个结论一起结算。
|
|
3121
|
+
*
|
|
3122
|
+
* 不比较取值本身:配了 names 或取值为对象/数组时拿到的是同一个引用,就地修改后比较不出变化。
|
|
3123
|
+
*/
|
|
3124
|
+
let generation = 0;
|
|
3125
|
+
let pendingSettlers = [];
|
|
3108
3126
|
return (asyncValidatorRule, value, callback, source, options) => {
|
|
3109
3127
|
const actualValue = props.config?.names ? props.model : value;
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3128
|
+
generation += 1;
|
|
3129
|
+
const currentGeneration = generation;
|
|
3130
|
+
/** 已有更新的一轮校验开始,本轮结论作废 */
|
|
3131
|
+
const isStale = () => currentGeneration !== generation;
|
|
3132
|
+
pendingSettlers.push((args) => callback(...args));
|
|
3133
|
+
/**
|
|
3134
|
+
* 结算本轮与被本轮取代的所有在途校验。
|
|
3135
|
+
*
|
|
3136
|
+
* async-validator 的 callback 不幂等:重复调用会让它的内部计数提前满足、错误信息重复;
|
|
3137
|
+
* 原始 validator 写成 async 时很容易既调 callback 又返回 Promise,故所有回调都收敛到这里,
|
|
3138
|
+
* 由「取出即出队」保证每个调用的 callback 只会被调用一次。
|
|
3139
|
+
*/
|
|
3140
|
+
const conclude = (...args) => {
|
|
3141
|
+
if (isStale()) return;
|
|
3142
|
+
const settlers = pendingSettlers;
|
|
3143
|
+
pendingSettlers = [];
|
|
3144
|
+
for (const settle of settlers) settle(args);
|
|
3145
|
+
};
|
|
3146
|
+
/**
|
|
3147
|
+
* 执行原始 validator 并把结果统一转成 callback。
|
|
3148
|
+
*
|
|
3149
|
+
* async-validator 只解析同步返回给它的返回值(true / false / Error / 错误数组 / Promise,
|
|
3150
|
+
* 抛错转成错误信息),异步路径已脱离它的调用栈,这些约定会失效导致校验永不结束,故复刻一份。
|
|
3151
|
+
*/
|
|
3152
|
+
const settleWithOriginalValidator = () => {
|
|
3153
|
+
if (isStale()) return;
|
|
3154
|
+
if (!originalValidator) {
|
|
3155
|
+
conclude();
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
let result;
|
|
3159
|
+
try {
|
|
3160
|
+
result = originalValidator({
|
|
3161
|
+
rule: asyncValidatorRule,
|
|
3162
|
+
value: actualValue,
|
|
3163
|
+
callback: conclude,
|
|
3164
|
+
source,
|
|
3165
|
+
options
|
|
3166
|
+
}, {
|
|
3167
|
+
values: mForm?.initValues || {},
|
|
3168
|
+
model: props.model,
|
|
3169
|
+
parent: mForm?.parentValues || {},
|
|
3170
|
+
formValue: mForm?.values || props.model,
|
|
3171
|
+
prop: props.prop,
|
|
3172
|
+
config: props.config
|
|
3173
|
+
}, mForm);
|
|
3174
|
+
} catch (err) {
|
|
3175
|
+
conclude(toError(err));
|
|
3114
3176
|
return;
|
|
3115
3177
|
}
|
|
3116
|
-
|
|
3117
|
-
|
|
3178
|
+
if (isPromise(result)) result.then(() => conclude(), (err) => conclude(toError(err)));
|
|
3179
|
+
else if (result === true) conclude();
|
|
3180
|
+
else if (result === false) {
|
|
3181
|
+
const field = asyncValidatorRule?.fullField || asyncValidatorRule?.field || props.prop;
|
|
3182
|
+
conclude(new Error(rule.message || `${field} fails`));
|
|
3183
|
+
} else if (result instanceof Error || Array.isArray(result)) conclude(result);
|
|
3184
|
+
};
|
|
3185
|
+
const skipFailedTypeMatch = (err) => {
|
|
3186
|
+
console.error(err);
|
|
3187
|
+
settleWithOriginalValidator();
|
|
3188
|
+
};
|
|
3189
|
+
let error;
|
|
3190
|
+
try {
|
|
3191
|
+
error = validateTypeMatch(actualValue, mForm, props, rule.message);
|
|
3192
|
+
} catch (err) {
|
|
3193
|
+
skipFailedTypeMatch(err);
|
|
3194
|
+
return;
|
|
3118
3195
|
}
|
|
3119
|
-
if (
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3196
|
+
if (isPromise(error)) {
|
|
3197
|
+
error.then((asyncMessage) => {
|
|
3198
|
+
if (asyncMessage) {
|
|
3199
|
+
conclude(new Error(asyncMessage));
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
settleWithOriginalValidator();
|
|
3203
|
+
}, skipFailedTypeMatch);
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
if (error) {
|
|
3207
|
+
conclude(new Error(error));
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3210
|
+
settleWithOriginalValidator();
|
|
3134
3211
|
};
|
|
3135
3212
|
};
|
|
3136
3213
|
//#endregion
|
|
@@ -3149,15 +3226,16 @@
|
|
|
3149
3226
|
const callback = (error) => {
|
|
3150
3227
|
if (settled) return;
|
|
3151
3228
|
settled = true;
|
|
3152
|
-
|
|
3229
|
+
const first = Array.isArray(error) ? error[0] : error;
|
|
3230
|
+
if (first) resolve({
|
|
3153
3231
|
result: false,
|
|
3154
|
-
message: typeof
|
|
3232
|
+
message: typeof first === "string" ? first : first.message
|
|
3155
3233
|
});
|
|
3156
3234
|
else resolve(true);
|
|
3157
3235
|
};
|
|
3158
3236
|
try {
|
|
3159
3237
|
const result = validator(void 0, value, callback);
|
|
3160
|
-
if (result
|
|
3238
|
+
if (result && typeof result.then === "function") Promise.resolve(result).then(() => {
|
|
3161
3239
|
if (!settled) callback();
|
|
3162
3240
|
}, (err) => {
|
|
3163
3241
|
callback(err instanceof Error ? err : new Error(String(err)));
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.8.0-beta.
|
|
2
|
+
"version": "1.8.0-beta.18",
|
|
3
3
|
"name": "@tmagic/form",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"vue": "^3.5.40",
|
|
54
54
|
"typescript": "^6.0.3",
|
|
55
|
-
"@tmagic/design": "1.8.0-beta.
|
|
56
|
-
"@tmagic/utils": "1.8.0-beta.
|
|
57
|
-
"@tmagic/form-schema": "1.8.0-beta.
|
|
55
|
+
"@tmagic/design": "1.8.0-beta.18",
|
|
56
|
+
"@tmagic/utils": "1.8.0-beta.18",
|
|
57
|
+
"@tmagic/form-schema": "1.8.0-beta.18"
|
|
58
58
|
},
|
|
59
59
|
"peerDependenciesMeta": {
|
|
60
60
|
"typescript": {
|
package/src/utils/form.ts
CHANGED
|
@@ -60,13 +60,15 @@ export const adaptFormValidator = (validator: AsyncValidatorFn): AsyncValidatorF
|
|
|
60
60
|
const value = arg1;
|
|
61
61
|
return new Promise((resolve) => {
|
|
62
62
|
let settled = false;
|
|
63
|
-
const callback = (error?: Error | string) => {
|
|
63
|
+
const callback = (error?: Error | string | (Error | string)[]) => {
|
|
64
64
|
if (settled) return;
|
|
65
65
|
settled = true;
|
|
66
|
-
|
|
66
|
+
// async-validator 约定 callback 也可接收错误数组,TDesign 只能展示一条,取首条
|
|
67
|
+
const first = Array.isArray(error) ? error[0] : error;
|
|
68
|
+
if (first) {
|
|
67
69
|
resolve({
|
|
68
70
|
result: false,
|
|
69
|
-
message: typeof
|
|
71
|
+
message: typeof first === 'string' ? first : first.message,
|
|
70
72
|
});
|
|
71
73
|
} else {
|
|
72
74
|
resolve(true);
|
|
@@ -74,8 +76,9 @@ export const adaptFormValidator = (validator: AsyncValidatorFn): AsyncValidatorF
|
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
try {
|
|
79
|
+
// 异步 validator 会先返回 undefined,稍后再调 callback,这里不能当成 thenable 取值
|
|
77
80
|
const result = validator(undefined, value, callback);
|
|
78
|
-
if (result
|
|
81
|
+
if (result && typeof (result as PromiseLike<unknown>).then === 'function') {
|
|
79
82
|
Promise.resolve(result).then(
|
|
80
83
|
() => {
|
|
81
84
|
if (!settled) callback();
|
package/src/utils/typeMatch.ts
CHANGED
|
@@ -38,8 +38,15 @@ export interface TypeMatchValidateContext {
|
|
|
38
38
|
// #endregion TypeMatchValidateContext
|
|
39
39
|
|
|
40
40
|
// #region TypeMatchValidator
|
|
41
|
-
/**
|
|
42
|
-
|
|
41
|
+
/**
|
|
42
|
+
* 自定义 type 校验器:返回错误文案;通过则返回 undefined。
|
|
43
|
+
*
|
|
44
|
+
* 支持返回 Promise,用于需要异步确认取值是否合法的场景(如请求接口校验 id 是否存在)。
|
|
45
|
+
*/
|
|
46
|
+
export type TypeMatchValidator = (
|
|
47
|
+
value: any,
|
|
48
|
+
context: TypeMatchValidateContext,
|
|
49
|
+
) => string | undefined | Promise<string | undefined>;
|
|
43
50
|
// #endregion TypeMatchValidator
|
|
44
51
|
|
|
45
52
|
const typeMatchRuleRegistry = new Map<string, TypeMatchValidator>();
|
|
@@ -692,12 +699,17 @@ const validateBuiltinTypeMatch = (
|
|
|
692
699
|
return undefined;
|
|
693
700
|
};
|
|
694
701
|
|
|
702
|
+
/**
|
|
703
|
+
* 校验取值与字段 type 是否匹配;通过返回 undefined,否则返回错误文案。
|
|
704
|
+
*
|
|
705
|
+
* 命中的自定义规则是异步校验器时返回 Promise,内置规则始终同步返回。
|
|
706
|
+
*/
|
|
695
707
|
export const validateTypeMatch = (
|
|
696
708
|
value: any,
|
|
697
709
|
mForm: FormState | undefined,
|
|
698
710
|
props: any,
|
|
699
711
|
message?: string,
|
|
700
|
-
): string | undefined => {
|
|
712
|
+
): string | undefined | Promise<string | undefined> => {
|
|
701
713
|
if (isEmptyValue(value) || isEmptyArray(value)) {
|
|
702
714
|
return undefined;
|
|
703
715
|
}
|
|
@@ -724,43 +736,135 @@ export const validateTypeMatch = (
|
|
|
724
736
|
return validateBuiltinTypeMatch(value, fieldType, mForm, props, message);
|
|
725
737
|
};
|
|
726
738
|
|
|
739
|
+
const toError = (error: any): Error => (error instanceof Error ? error : new Error(`${error}`));
|
|
740
|
+
|
|
727
741
|
export const createTypeMatchValidator = (mForm: FormState | undefined, props: any, rule: Rule) => {
|
|
728
742
|
const originalValidator = typeof rule.validator === 'function' ? rule.validator : undefined;
|
|
729
743
|
|
|
744
|
+
/**
|
|
745
|
+
* 每次校验取一个自增序号,只有最新一轮的结论会被采用。
|
|
746
|
+
*
|
|
747
|
+
* 旧值的在途校验不能用自己的结论结算:它可能晚于新校验结束,用过期结论覆盖新结论
|
|
748
|
+
* (表单取最后结束的那次结果);也不能在新校验开始时无条件按通过结算,否则
|
|
749
|
+
* `form.validate()` 会对一个还没校验过的值返回成功。因此所有尚未结算的调用都登记在
|
|
750
|
+
* pendingSettlers 中,等最新一轮出结论后用同一个结论一起结算。
|
|
751
|
+
*
|
|
752
|
+
* 不比较取值本身:配了 names 或取值为对象/数组时拿到的是同一个引用,就地修改后比较不出变化。
|
|
753
|
+
*/
|
|
754
|
+
let generation = 0;
|
|
755
|
+
let pendingSettlers: ((args: any[]) => void)[] = [];
|
|
756
|
+
|
|
730
757
|
return (asyncValidatorRule: any, value: any, callback: Function, source: any, options: any) => {
|
|
731
758
|
const actualValue = props.config?.names ? props.model : value;
|
|
732
|
-
try {
|
|
733
|
-
const error = validateTypeMatch(actualValue, mForm, props, rule.message);
|
|
734
759
|
|
|
735
|
-
|
|
736
|
-
|
|
760
|
+
generation += 1;
|
|
761
|
+
const currentGeneration = generation;
|
|
762
|
+
/** 已有更新的一轮校验开始,本轮结论作废 */
|
|
763
|
+
const isStale = () => currentGeneration !== generation;
|
|
764
|
+
|
|
765
|
+
pendingSettlers.push((args: any[]) => callback(...args));
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* 结算本轮与被本轮取代的所有在途校验。
|
|
769
|
+
*
|
|
770
|
+
* async-validator 的 callback 不幂等:重复调用会让它的内部计数提前满足、错误信息重复;
|
|
771
|
+
* 原始 validator 写成 async 时很容易既调 callback 又返回 Promise,故所有回调都收敛到这里,
|
|
772
|
+
* 由「取出即出队」保证每个调用的 callback 只会被调用一次。
|
|
773
|
+
*/
|
|
774
|
+
const conclude = (...args: any[]) => {
|
|
775
|
+
if (isStale()) {
|
|
737
776
|
return;
|
|
738
777
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
778
|
+
|
|
779
|
+
const settlers = pendingSettlers;
|
|
780
|
+
pendingSettlers = [];
|
|
781
|
+
for (const settle of settlers) {
|
|
782
|
+
settle(args);
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* 执行原始 validator 并把结果统一转成 callback。
|
|
788
|
+
*
|
|
789
|
+
* async-validator 只解析同步返回给它的返回值(true / false / Error / 错误数组 / Promise,
|
|
790
|
+
* 抛错转成错误信息),异步路径已脱离它的调用栈,这些约定会失效导致校验永不结束,故复刻一份。
|
|
791
|
+
*/
|
|
792
|
+
const settleWithOriginalValidator = () => {
|
|
793
|
+
// 本轮结论已作废,不必再跑一遍原始 validator 触发副作用
|
|
794
|
+
if (isStale()) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (!originalValidator) {
|
|
799
|
+
conclude();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
let result: any;
|
|
804
|
+
try {
|
|
805
|
+
result = originalValidator(
|
|
806
|
+
{ rule: asyncValidatorRule, value: actualValue, callback: conclude, source, options },
|
|
807
|
+
{
|
|
808
|
+
values: mForm?.initValues || {},
|
|
809
|
+
model: props.model,
|
|
810
|
+
parent: mForm?.parentValues || {},
|
|
811
|
+
formValue: mForm?.values || props.model,
|
|
812
|
+
prop: props.prop,
|
|
813
|
+
config: props.config,
|
|
814
|
+
},
|
|
815
|
+
mForm,
|
|
816
|
+
);
|
|
817
|
+
} catch (err) {
|
|
818
|
+
conclude(toError(err));
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
if (isPromise(result)) {
|
|
823
|
+
result.then(
|
|
824
|
+
() => conclude(),
|
|
825
|
+
(err: any) => conclude(toError(err)),
|
|
826
|
+
);
|
|
827
|
+
} else if (result === true) {
|
|
828
|
+
conclude();
|
|
829
|
+
} else if (result === false) {
|
|
830
|
+
const field = asyncValidatorRule?.fullField || asyncValidatorRule?.field || props.prop;
|
|
831
|
+
conclude(new Error(rule.message || `${field} fails`));
|
|
832
|
+
} else if (result instanceof Error || Array.isArray(result)) {
|
|
833
|
+
conclude(result);
|
|
834
|
+
}
|
|
835
|
+
// 其余返回值(undefined / void)按约定由 validator 自行调用 callback
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// 校验器自身失败(如接口异常)不应阻塞用户,记录后按通过处理
|
|
839
|
+
const skipFailedTypeMatch = (err: any) => {
|
|
840
|
+
console.error(err);
|
|
841
|
+
settleWithOriginalValidator();
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
let error: string | undefined | Promise<string | undefined>;
|
|
845
|
+
try {
|
|
846
|
+
error = validateTypeMatch(actualValue, mForm, props, rule.message);
|
|
847
|
+
} catch (err) {
|
|
848
|
+
skipFailedTypeMatch(err);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (isPromise(error)) {
|
|
853
|
+
error.then((asyncMessage) => {
|
|
854
|
+
if (asyncMessage) {
|
|
855
|
+
conclude(new Error(asyncMessage));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
settleWithOriginalValidator();
|
|
859
|
+
}, skipFailedTypeMatch);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (error) {
|
|
864
|
+
conclude(new Error(error));
|
|
865
|
+
return;
|
|
762
866
|
}
|
|
763
867
|
|
|
764
|
-
|
|
868
|
+
settleWithOriginalValidator();
|
|
765
869
|
};
|
|
766
870
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -2206,8 +2206,12 @@ interface TypeMatchValidateContext {
|
|
|
2206
2206
|
props: any;
|
|
2207
2207
|
message?: string;
|
|
2208
2208
|
}
|
|
2209
|
-
/**
|
|
2210
|
-
|
|
2209
|
+
/**
|
|
2210
|
+
* 自定义 type 校验器:返回错误文案;通过则返回 undefined。
|
|
2211
|
+
*
|
|
2212
|
+
* 支持返回 Promise,用于需要异步确认取值是否合法的场景(如请求接口校验 id 是否存在)。
|
|
2213
|
+
*/
|
|
2214
|
+
type TypeMatchValidator = (value: any, context: TypeMatchValidateContext) => string | undefined | Promise<string | undefined>;
|
|
2211
2215
|
/** 注册或覆盖某个字段 type 的 typeMatch 校验规则 */
|
|
2212
2216
|
declare const registerTypeMatchRule: (type: string, validator: TypeMatchValidator) => void;
|
|
2213
2217
|
/** 批量注册 typeMatch 校验规则 */
|
|
@@ -2218,7 +2222,12 @@ declare const getTypeMatchRule: (type: string) => TypeMatchValidator | undefined
|
|
|
2218
2222
|
declare const deleteTypeMatchRule: (type: string) => boolean;
|
|
2219
2223
|
/** 清空所有自定义 typeMatch 校验规则 */
|
|
2220
2224
|
declare const clearTypeMatchRules: () => void;
|
|
2221
|
-
|
|
2225
|
+
/**
|
|
2226
|
+
* 校验取值与字段 type 是否匹配;通过返回 undefined,否则返回错误文案。
|
|
2227
|
+
*
|
|
2228
|
+
* 命中的自定义规则是异步校验器时返回 Promise,内置规则始终同步返回。
|
|
2229
|
+
*/
|
|
2230
|
+
declare const validateTypeMatch: (value: any, mForm: schema_d_exports.FormState | undefined, props: any, message?: string) => string | undefined | Promise<string | undefined>;
|
|
2222
2231
|
//#endregion
|
|
2223
2232
|
//#region temp/packages/form/src/plugin.d.ts
|
|
2224
2233
|
interface FormInstallOptions {
|