fix(form): fix async-validator related types

This commit is contained in:
07akioni 2021-05-19 02:23:47 +08:00
parent 7e9784a6d3
commit df73044c24
5 changed files with 71 additions and 50 deletions

View File

@ -11,18 +11,18 @@ You can use `n-form-item` alone, without `n-form`.
```js ```js
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue'
const lyrics = 'It is not in Form' const message = 'It is not in Form'
export default defineComponent({ export default defineComponent({
setup () { setup () {
const valueRef = ref(lyrics) const valueRef = ref(message)
return { return {
value: valueRef, value: valueRef,
rule: { rule: {
trigger: ['input', 'blur'], trigger: ['input', 'blur'],
validator () { validator () {
if (valueRef.value !== lyrics) { if (valueRef.value !== message) {
return new Error(lyrics) return new Error(message)
} }
} }
} }

View File

@ -11,18 +11,18 @@
```js ```js
import { defineComponent, ref } from 'vue' import { defineComponent, ref } from 'vue'
const lyrics = '它不在 Form 里面' const message = '它不在 Form 里面'
export default defineComponent({ export default defineComponent({
setup () { setup () {
const valueRef = ref(lyrics) const valueRef = ref(message)
return { return {
value: valueRef, value: valueRef,
rule: { rule: {
trigger: ['input', 'blur'], trigger: ['input', 'blur'],
validator () { validator () {
if (valueRef.value !== lyrics) { if (valueRef.value !== message) {
return new Error(lyrics) return new Error(message)
} }
} }
} }

View File

@ -13,7 +13,7 @@ import {
Transition, Transition,
renderSlot renderSlot
} from 'vue' } from 'vue'
import Schema, { ErrorList, ValidateOption } from 'async-validator' import Schema, { ErrorList, RuleItem, ValidateOption } from 'async-validator'
import { get } from 'lodash-es' import { get } from 'lodash-es'
import { createId } from 'seemly' import { createId } from 'seemly'
import { formItemInjectionKey } from '../../_mixins/use-form-item' import { formItemInjectionKey } from '../../_mixins/use-form-item'
@ -36,6 +36,7 @@ import {
LabelPlacement, LabelPlacement,
ValidateCallback, ValidateCallback,
ValidationTrigger, ValidationTrigger,
FormItemRuleValidatorParams,
FormItemRuleValidator, FormItemRuleValidator,
FormItemValidateOptions, FormItemValidateOptions,
FormItemInst, FormItemInst,
@ -87,27 +88,38 @@ export type FormItemProps = ExtractPublicPropTypes<typeof formItemProps>
export const formItemPropKeys = keysOf(formItemProps) export const formItemPropKeys = keysOf(formItemProps)
// Wrapped Validator is to be passed into async-validator // 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 = ( type WrappedValidator = (
...args: Parameters<FormItemRuleValidator> ...args: FormItemRuleValidatorParams
) => boolean | Error | Promise<boolean | Error> | undefined ) => boolean | Error | Error[] | Promise<void> | undefined
function wrapValidator (validator: FormItemRuleValidator): WrappedValidator {
// wrap sync validator
function wrapValidator (
validator: FormItemRuleValidator,
async: boolean
): WrappedValidator {
return (...args: Parameters<FormItemRuleValidator>) => { return (...args: Parameters<FormItemRuleValidator>) => {
try { try {
const validateResult = validator(...args) const validateResult = validator(...args)
if ( if (
typeof validateResult === 'boolean' || (!async &&
validateResult instanceof Error || (typeof validateResult === 'boolean' ||
validateResult?.then validateResult instanceof Error ||
Array.isArray(validateResult))) || // Error[]
(validateResult as any)?.then
) { ) {
return validateResult return validateResult as any
} else if (validateResult === undefined) { } else if (validateResult === undefined) {
return true return true
} else { } else {
warn( warn(
'form-item/validate', 'form-item/validate',
`You return a ${typeof validateResult} ` + `You return a ${typeof validateResult} ` +
'typed value in the validator method, which is not recommended. Please ' + 'typed value in the validator method, which is not recommended. Please use ' +
'use `boolean`, `Error` or `Promise` typed value instead.' (async ? '`Promise`' : '`boolean`, `Error` or `Promise`') +
' typed value instead.'
) )
return true return true
} }
@ -119,6 +131,8 @@ function wrapValidator (validator: FormItemRuleValidator): WrappedValidator {
"`n-form` or `n-form-item` won't be called in this validation." "`n-form` or `n-form-item` won't be called in this validation."
) )
console.error(err) 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 return undefined
} }
} }
@ -138,10 +152,8 @@ export default defineComponent({
const formItemSizeRefs = formItemSize(props) const formItemSizeRefs = formItemSize(props)
const formItemMiscRefs = formItemMisc(props) const formItemMiscRefs = formItemMisc(props)
const { validationErrored: validationErroredRef } = formItemMiscRefs const { validationErrored: validationErroredRef } = formItemMiscRefs
const { const { mergedRequired: mergedRequiredRef, mergedRules: mergedRulesRef } =
mergedRequired: mergedRequiredRef, formItemRule(props)
mergedRules: mergedRulesRef
} = formItemRule(props)
const { mergedSize: mergedSizeRef } = formItemSizeRefs const { mergedSize: mergedSizeRef } = formItemSizeRefs
const { mergedLabelPlacement: labelPlacementRef } = formItemMiscRefs const { mergedLabelPlacement: labelPlacementRef } = formItemMiscRefs
const explainsRef = ref<string[]>([]) const explainsRef = ref<string[]>([])
@ -247,28 +259,31 @@ export default defineComponent({
const { value: rules } = mergedRulesRef const { value: rules } = mergedRulesRef
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const value = NForm ? get(NForm.model, path!, null) : undefined const value = NForm ? get(NForm.model, path!, null) : undefined
const activeRules = (!trigger const activeRules = (
? rules !trigger
: rules.filter((rule) => { ? rules
// if (rule.trigger === undefined) return true : rules.filter((rule) => {
if (Array.isArray(rule.trigger)) { // if (rule.trigger === undefined) return true
return rule.trigger.includes(trigger) if (Array.isArray(rule.trigger)) {
} else { return rule.trigger.includes(trigger)
return rule.trigger === trigger } else {
} return rule.trigger === trigger
}) }
})
) )
.filter(shouldRuleBeApplied) .filter(shouldRuleBeApplied)
.map((rule) => { .map((rule) => {
const shallowClonedRule = Object.assign({}, rule) const shallowClonedRule = Object.assign({}, rule)
if (shallowClonedRule.validator) { if (shallowClonedRule.validator) {
shallowClonedRule.validator = wrapValidator( shallowClonedRule.validator = wrapValidator(
shallowClonedRule.validator shallowClonedRule.validator,
false
) )
} }
if (shallowClonedRule.asyncValidator) { if (shallowClonedRule.asyncValidator) {
shallowClonedRule.asyncValidator = wrapValidator( shallowClonedRule.asyncValidator = wrapValidator(
shallowClonedRule.asyncValidator shallowClonedRule.asyncValidator,
true
) as any ) as any
} }
return shallowClonedRule return shallowClonedRule
@ -279,8 +294,8 @@ export default defineComponent({
}) })
} }
const mergedPath = path ?? '__n_no_path__' const mergedPath = path ?? '__n_no_path__'
const validator = new Schema({ [mergedPath]: activeRules }) const validator = new Schema({ [mergedPath]: activeRules as RuleItem[] })
return new Promise((resolve, reject) => { return new Promise((resolve) => {
void validator.validate( void validator.validate(
{ [mergedPath]: value }, { [mergedPath]: value },
options, options,
@ -345,10 +360,8 @@ export default defineComponent({
[createKey('feedbackHeight', size)]: feedbackHeight, [createKey('feedbackHeight', size)]: feedbackHeight,
[createKey('labelPadding', direction)]: labelPadding, [createKey('labelPadding', direction)]: labelPadding,
[createKey('labelTextAlign', direction)]: labelTextAlign, [createKey('labelTextAlign', direction)]: labelTextAlign,
[createKey( [createKey(createKey('labelFontSize', labelPlacement), size)]:
createKey('labelFontSize', labelPlacement), labelFontSize
size
)]: labelFontSize
} }
} = themeRef.value } = themeRef.value
return { return {

View File

@ -1,4 +1,4 @@
import { InjectionKey } from '@vue/runtime-core' import { InjectionKey } from 'vue'
import { ErrorList, RuleItem, ValidateOption } from 'async-validator' import { ErrorList, RuleItem, ValidateOption } from 'async-validator'
import { FormSetupProps } from './Form' import { FormSetupProps } from './Form'
@ -6,14 +6,23 @@ export interface FormRules {
[path: string]: FormRules | FormItemRule | FormItemRule[] [path: string]: FormRules | FormItemRule | FormItemRule[]
} }
export type FormItemRuleValidator = ( export type FormItemRuleValidatorParams = Parameters<
...args: Parameters<RuleItem['validator'] & {}> NonNullable<RuleItem['validator']>
) => boolean | Error | Promise<boolean> | Promise<Error> | any >
export type FormItemRule = RuleItem & { export type FormItemRuleValidator = (
...args: FormItemRuleValidatorParams
) => boolean | Error | Error[] | Promise<void> | undefined
// In src of async-validator, any non-promise of asyncValidator will be abadoned
export type FormItemRuleAsyncValidator = (
...args: FormItemRuleValidatorParams
) => Promise<void> | undefined
export type FormItemRule = Omit<RuleItem, 'validator' | 'asyncValidator'> & {
trigger?: ValidationTrigger | string | Array<ValidationTrigger | string> trigger?: ValidationTrigger | string | Array<ValidationTrigger | string>
validator?: FormItemRuleValidator validator?: FormItemRuleValidator
asyncValidator?: FormItemRuleValidator asyncValidator?: FormItemRuleAsyncValidator
} }
export interface FormItemValidateOptions { export interface FormItemValidateOptions {
@ -50,9 +59,8 @@ export type FormItemRowRef = FormItemInst
export type FormInjection = FormSetupProps export type FormInjection = FormSetupProps
export const formInjectionKey: InjectionKey<FormInjection> = Symbol('form') export const formInjectionKey: InjectionKey<FormInjection> = Symbol('form')
export const formItemInstsInjectionKey: InjectionKey<unknown> = Symbol( export const formItemInstsInjectionKey: InjectionKey<unknown> =
'formItemInsts' Symbol('formItemInsts')
)
export type LabelAlign = 'left' | 'center' | 'right' export type LabelAlign = 'left' | 'center' | 'right'
export type LabelPlacement = 'left' | 'top' export type LabelPlacement = 'left' | 'top'

View File

@ -1 +1 @@
export default '2.7.4' export default '2.8.0'