From df73044c243a89ec29e6ef4eaa370b5db958c349 Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Wed, 19 May 2021 02:23:47 +0800 Subject: [PATCH] fix(form): fix async-validator related types --- src/form/demos/enUS/item-only.demo.md | 8 +-- src/form/demos/zhCN/item-only.demo.md | 8 +-- src/form/src/FormItem.tsx | 77 ++++++++++++++++----------- src/form/src/interface.ts | 26 +++++---- src/version.ts | 2 +- 5 files changed, 71 insertions(+), 50 deletions(-) diff --git a/src/form/demos/enUS/item-only.demo.md b/src/form/demos/enUS/item-only.demo.md index d5844cd35..540d36bce 100644 --- a/src/form/demos/enUS/item-only.demo.md +++ b/src/form/demos/enUS/item-only.demo.md @@ -11,18 +11,18 @@ You can use `n-form-item` alone, without `n-form`. ```js import { defineComponent, ref } from 'vue' -const lyrics = 'It is not in Form' +const message = 'It is not in Form' export default defineComponent({ setup () { - const valueRef = ref(lyrics) + const valueRef = ref(message) return { value: valueRef, rule: { trigger: ['input', 'blur'], validator () { - if (valueRef.value !== lyrics) { - return new Error(lyrics) + if (valueRef.value !== message) { + return new Error(message) } } } diff --git a/src/form/demos/zhCN/item-only.demo.md b/src/form/demos/zhCN/item-only.demo.md index a355b8ea2..109ab2a80 100644 --- a/src/form/demos/zhCN/item-only.demo.md +++ b/src/form/demos/zhCN/item-only.demo.md @@ -11,18 +11,18 @@ ```js import { defineComponent, ref } from 'vue' -const lyrics = '它不在 Form 里面' +const message = '它不在 Form 里面' export default defineComponent({ setup () { - const valueRef = ref(lyrics) + const valueRef = ref(message) return { value: valueRef, rule: { trigger: ['input', 'blur'], validator () { - if (valueRef.value !== lyrics) { - return new Error(lyrics) + if (valueRef.value !== message) { + return new Error(message) } } } diff --git a/src/form/src/FormItem.tsx b/src/form/src/FormItem.tsx index d4361bc76..9252815c4 100644 --- a/src/form/src/FormItem.tsx +++ b/src/form/src/FormItem.tsx @@ -13,7 +13,7 @@ import { Transition, renderSlot } from 'vue' -import Schema, { ErrorList, ValidateOption } from 'async-validator' +import Schema, { ErrorList, RuleItem, ValidateOption } from 'async-validator' import { get } from 'lodash-es' import { createId } from 'seemly' import { formItemInjectionKey } from '../../_mixins/use-form-item' @@ -36,6 +36,7 @@ import { LabelPlacement, ValidateCallback, ValidationTrigger, + FormItemRuleValidatorParams, FormItemRuleValidator, FormItemValidateOptions, FormItemInst, @@ -87,27 +88,38 @@ export type FormItemProps = ExtractPublicPropTypes export const formItemPropKeys = keysOf(formItemProps) // Wrapped Validator is to be passed into async-validator +// In their source code, validator can be a asyncValidator. +// asyncValidator will non-promise return value will be ignored. +// We need to deal with some type quirks. type WrappedValidator = ( - ...args: Parameters -) => boolean | Error | Promise | undefined -function wrapValidator (validator: FormItemRuleValidator): WrappedValidator { + ...args: FormItemRuleValidatorParams +) => boolean | Error | Error[] | Promise | undefined + +// wrap sync validator +function wrapValidator ( + validator: FormItemRuleValidator, + async: boolean +): WrappedValidator { return (...args: Parameters) => { try { const validateResult = validator(...args) if ( - typeof validateResult === 'boolean' || - validateResult instanceof Error || - validateResult?.then + (!async && + (typeof validateResult === 'boolean' || + validateResult instanceof Error || + Array.isArray(validateResult))) || // Error[] + (validateResult as any)?.then ) { - return validateResult + return validateResult as any } else if (validateResult === undefined) { return true } else { warn( 'form-item/validate', `You return a ${typeof validateResult} ` + - 'typed value in the validator method, which is not recommended. Please ' + - 'use `boolean`, `Error` or `Promise` typed value instead.' + 'typed value in the validator method, which is not recommended. Please use ' + + (async ? '`Promise`' : '`boolean`, `Error` or `Promise`') + + ' typed value instead.' ) return true } @@ -119,6 +131,8 @@ function wrapValidator (validator: FormItemRuleValidator): WrappedValidator { "`n-form` or `n-form-item` won't be called in this validation." ) console.error(err) + // If returns undefined, async-validator won't trigger callback + // so the result will be abandoned, which means not true and not false return undefined } } @@ -138,10 +152,8 @@ export default defineComponent({ const formItemSizeRefs = formItemSize(props) const formItemMiscRefs = formItemMisc(props) const { validationErrored: validationErroredRef } = formItemMiscRefs - const { - mergedRequired: mergedRequiredRef, - mergedRules: mergedRulesRef - } = formItemRule(props) + const { mergedRequired: mergedRequiredRef, mergedRules: mergedRulesRef } = + formItemRule(props) const { mergedSize: mergedSizeRef } = formItemSizeRefs const { mergedLabelPlacement: labelPlacementRef } = formItemMiscRefs const explainsRef = ref([]) @@ -247,28 +259,31 @@ export default defineComponent({ const { value: rules } = mergedRulesRef // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const value = NForm ? get(NForm.model, path!, null) : undefined - const activeRules = (!trigger - ? rules - : rules.filter((rule) => { - // if (rule.trigger === undefined) return true - if (Array.isArray(rule.trigger)) { - return rule.trigger.includes(trigger) - } else { - return rule.trigger === trigger - } - }) + const activeRules = ( + !trigger + ? rules + : rules.filter((rule) => { + // if (rule.trigger === undefined) return true + if (Array.isArray(rule.trigger)) { + return rule.trigger.includes(trigger) + } else { + return rule.trigger === trigger + } + }) ) .filter(shouldRuleBeApplied) .map((rule) => { const shallowClonedRule = Object.assign({}, rule) if (shallowClonedRule.validator) { shallowClonedRule.validator = wrapValidator( - shallowClonedRule.validator + shallowClonedRule.validator, + false ) } if (shallowClonedRule.asyncValidator) { shallowClonedRule.asyncValidator = wrapValidator( - shallowClonedRule.asyncValidator + shallowClonedRule.asyncValidator, + true ) as any } return shallowClonedRule @@ -279,8 +294,8 @@ export default defineComponent({ }) } const mergedPath = path ?? '__n_no_path__' - const validator = new Schema({ [mergedPath]: activeRules }) - return new Promise((resolve, reject) => { + const validator = new Schema({ [mergedPath]: activeRules as RuleItem[] }) + return new Promise((resolve) => { void validator.validate( { [mergedPath]: value }, options, @@ -345,10 +360,8 @@ export default defineComponent({ [createKey('feedbackHeight', size)]: feedbackHeight, [createKey('labelPadding', direction)]: labelPadding, [createKey('labelTextAlign', direction)]: labelTextAlign, - [createKey( - createKey('labelFontSize', labelPlacement), - size - )]: labelFontSize + [createKey(createKey('labelFontSize', labelPlacement), size)]: + labelFontSize } } = themeRef.value return { diff --git a/src/form/src/interface.ts b/src/form/src/interface.ts index e1d97d6bf..d61818837 100644 --- a/src/form/src/interface.ts +++ b/src/form/src/interface.ts @@ -1,4 +1,4 @@ -import { InjectionKey } from '@vue/runtime-core' +import { InjectionKey } from 'vue' import { ErrorList, RuleItem, ValidateOption } from 'async-validator' import { FormSetupProps } from './Form' @@ -6,14 +6,23 @@ export interface FormRules { [path: string]: FormRules | FormItemRule | FormItemRule[] } -export type FormItemRuleValidator = ( - ...args: Parameters -) => boolean | Error | Promise | Promise | any +export type FormItemRuleValidatorParams = Parameters< +NonNullable +> -export type FormItemRule = RuleItem & { +export type FormItemRuleValidator = ( + ...args: FormItemRuleValidatorParams +) => boolean | Error | Error[] | Promise | undefined + +// In src of async-validator, any non-promise of asyncValidator will be abadoned +export type FormItemRuleAsyncValidator = ( + ...args: FormItemRuleValidatorParams +) => Promise | undefined + +export type FormItemRule = Omit & { trigger?: ValidationTrigger | string | Array validator?: FormItemRuleValidator - asyncValidator?: FormItemRuleValidator + asyncValidator?: FormItemRuleAsyncValidator } export interface FormItemValidateOptions { @@ -50,9 +59,8 @@ export type FormItemRowRef = FormItemInst export type FormInjection = FormSetupProps export const formInjectionKey: InjectionKey = Symbol('form') -export const formItemInstsInjectionKey: InjectionKey = Symbol( - 'formItemInsts' -) +export const formItemInstsInjectionKey: InjectionKey = + Symbol('formItemInsts') export type LabelAlign = 'left' | 'center' | 'right' export type LabelPlacement = 'left' | 'top' diff --git a/src/version.ts b/src/version.ts index 8dd99f744..885452d10 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export default '2.7.4' +export default '2.8.0'