来看看 element-ui 中 form 表单 的验证

2,573 阅读6分钟

前言

element-ui 这个 ui 库许多前端开发都用过, 它的口号的是 网站快速成型工具
上周在使用的过程中,form 组件配置了所需的校验配置项,但是 validate 校验一直失败,今天来深入了解一下

首先来看,formelement-ui 中的用法,更多用法👉 element-ui#form

  <el-form
    ref="ruleFormRef"
    :model="ruleForm"
    :rules="rules"
  >
  
    <el-form-item label="Password" prop="pass">
     <el-input v-model="ruleForm.pass" type="password" autocomplete="off" />
    </el-form-item>
    
    // 提交按钮
    <el-form-item>
      <el-button type="primary" @click="submitForm(ruleFormRef)">
        Create
      </el-button>
    </el-form-item>
    
</el-form>

<script lang="ts" setup>
 
   const ruleFormRef = ref<FormInstance>()
    
    // 数据源
  const ruleForm = reactive({
      name: 'Hello',
   })
    
    // 定义校验规则
    const rules = reactive<FormRules>({
      pass: [
        { required: true, message: 'Please input Activity name', trigger: 'blur' },
        { min: 3, max: 5, message: 'Length should be 3 to 5', trigger: 'blur' },
      ]
     })
    
    // 执行校验
    const submitForm = async (formEl: FormInstance | undefined) => {
      if (!formEl) return
      await formEl.validate((valid, fields) => {
        if (valid) {
          console.log('submit!')
        } else {
          console.log('error submit!', fields)
          return false
        }
      })
    }
 
</script>

在上述代码中 :
el-form 中接收配置 model,rules 属性
el-form-item 接收 prop 属性,这个属性必须存在于 rules
el-input 中使用 v-model 连接 model 中的某一个属性
在校验的时候,使用的 el-form 中的 validate 方法


起步

在执行上述代码 validate 的这个地方 打一个 debugger , 来大致过一下流程

image.png

  1. 首先来到 form.vue 文件,在form.vue 文件中执行 validate 方法,接收一个 callback,然后去执行了 validateField(undefined, callback) (🔥 注意: 第一个参数是 undefined,第二个才是callback)

form.vue

image.png

  1. 来到validateField 函数。其中的第一个参数 modelProps(默认值[])(刚才的 undefined 被重置为 []), 第二个参数是我们熟悉的 callback 函数

函数内部 执行 doValidateField 方法,把我们刚才的 modelProps作为入参传入,返回值 result 作为 callback 的实参传入, 🔥 也就是说,这个 result 决定 validate 是否校验成功

image.png

这个就是大致的整体流程,validate --> validateField --> doValidateField

核心就是在 doValidateField 里面, doValidateField 的返回结果决定着校验的结果

🔥 el-form 辅助校验

1. doValidateField 起点

依然是接收一个参数props,对于我们的简单例子来说,是一个空数组

内部的 obtainValidateFields 方法接收了 props
obtainValidateFields 返回一个 fields 数组

fields 进行判断

  1. 如果是空,直接返回 true
  2. 如果不为空,则进行校验,把错误信息收集到validationErrors 对象上,如果没有错误,也是返回 true ,否则就是返回 Promise.reject

image.png

内部方法obtainValidateFields 返回的 fields 是一个核心方法,接着看它做了什么

2. obtainValidateFields

可以看到,这个方法拿着 props 走进 filterFields 方法中,但是出现了一个新的变量 fields,这个是从哪里来的呢?🧐

image.png

不得不提一下 fileds,因为这个属性很重要,我们稍微偏移下轨道🚆

  • fileds 的身世

el-form

const fields: FormItemContext[] = []

const addField: FormContext['addField'] = (field) => {
 fields.push(field)
}

ok, 在 el-form 中定义了 addField 方法,但是它自己却没有用到,看到这里,想必有经验的同学已经猜到了,这个方式是为了收集子组件(也可能不是直接子组件) el-form-item 中的属性

果不其然,在 form.vue 文件最后,使用了 provide 方法把 addField 提供了出去👮‍♀️ ( 这个地方也解释了formEl.resetFields()的来源 )

👇 是 provide 提供的部分方法

provide(formContextKey,reactive({
    resetFields,
    clearValidate,
    validateField,
    addField,
}))

既然知道了el-form-item 要使用,那么来到el-form-item文件,(为了方便理解,只保留了重要属性)

el-form-item

const formContext = inject(formContextKey, undefined)

const context: FormItemContext = reactive({
   // ....
  resetField,
  clearValidate,
  validate,
})


onMounted(() => {
  if (props.prop) {
    formContext?.addField(context)
  }
})

addFieldcontext 属性在 onMounted 的时候全部给拿走了

ok,这个就是 fields 的前世今生,fields 就是一个一个 el-form-item 中的属性方法组成的数组集合,明白了这个,我们回到正轨🚆

在上文的 ### obtainValidateFields 方法中,用到了一个工具函数 filterFields

filterFields

这个不在 form.vue 文件中,在同级目录 utils 文件

uitls.ts

image.png

由于props是一个 [],那么 normalized 也是一个空数组,所以,filterFields在这种情况下返回的就是fields 数组

👇图 为 fields 数组

image.png

🚀 校验

绕了一大圈,又回到了这里来,是不是已经忘了这个方法,没关系,我们来回看一下,(element-puls调用链是挺长的

graph TD
validate --> validateField
validateField-->doValidateField
doValidateField-->obtainValidateFields
obtainValidateFields --过滤fileds-->filterFields

obtainValidateFields 结束了,返回一个 fields, 进行遍历 fields,使用 try catch 包裹捕获错误

image.png

这个是校验的代码,执行 filed(即form-item) 中的 validate 方法
每一个 form-item 单独执行自己的校验,然后结果由 el-form 捕获

ok, 来看看 form-itemvalidate 方法

🔥🔥 el-form-item 核心校验

el-form-item

validate

const validate: FormItemContext['validate'] = async (trigger, callback) => {
   const rules = getFilteredRule(trigger)
   console.log("🚀~ rules:", rules);
   const hasCallback = isFunction(callback)
      //...
  
   
   return doValidate(rules)
    .then(() => {
      callback?.(true)
      return true as const
    })
    .catch((err: FormValidateFailure) => {
      const { fields } = err
      callback?.(false, fields)
      return hasCallback ? false : Promise.reject(fields)
    })
}

validateel-from 中只接收了 一个 空字符串'',那 trigger 就是一个 空字符, callback 被赋值为 undefined

这个 validate 方法在 el-input 中也需要用到,那个时候 的 trigger 要换成 blur或者 change 事件了

内部方法getFilteredRule 拿着这个 trigger 返回了一个过滤后的 rules, 最后使用 maptrigger给去掉了

👇为过滤后的 rules结果, 基本上没有变化,把 trigger:blur 去掉了

image.png

上文用到的 getFilteredRule 方法,过滤 rules, 不做过多描述

image.png

doValidate 拿着过滤后的 rules 去做 valide (不得不说,函数名字起的真好👍)

压力来到了doValidate 方法,它接收过滤后的 rulesdoValidate是一个 promise方法,不仅需要在 then 方法中执行 callback(true),还需要在 catch 抛出错误

👨‍🚀 doValidate

重要! 重要! 重要!

是核心的校验方法,为了便于理解, rules 和 modelName 都以注释的形式放上去了, **使用AsyncValidator进行校验 rulesmodelName ** code.png

AsyncValidator 是何许人也?

放一段 npm#AsyncValidator 官网上的代码,看完就明白了,el-form 的校验全靠它了

image.png

对照着我上文贴的 代码Shema 替换成 AsyncValidator,description 替换成 [modelName]: rules

image.png

image.png

偏离一下轨道🚆,讲解一下 fieldValue 的来源

image.png

记得formContext 的来源吗?

formContext 来自于 el-form 中的 provide值,model 就是定义在 el-form 的那个大对象,props.prop 是定义在 el-form-item 上的具体属性

当知道了 fieldValue.value 之后,我们 回到正轨 🚆

当 校验成功之后,会走进validator 中的 then 回调,执行 onValidationSucceeded

// 错误消息
const validateMessage = ref('')
// 验证状态
const validateState = ref<FormItemValidateState>('')

const setValidationState = (state: FormItemValidateState) => {
  validateState.value = state
}

const onValidationSucceeded = () => {
  setValidationState('success')
}

const onValidationFailed = (error: FormValidateFailure) => {
  const { errors, fields } = error
  if (!errors || !fields) {
    console.error(error)
  }

  validateMessage.value = errors
    ? errors?.[0]?.message ?? `${props.prop} is required`
    : ''
 
}

onValidationSucceeded 和 onValidationFailed 中 使用 方法setValidationState 设置了不同的校验状态onValidationFailed 中设置了 validateMessage 错误消息

在页面上使用

image.png 传递了一个具名的 error 插槽 ,同时把validateMessage 给抛出来

image.png

以上就是当你按下 开始校验按钮 之后的全部过程

有朋友要问了,你说的是 点击校验按钮 才能校验,那有时候blur / change 也可以执行校验,是为什么呢?

别急,还记得 elf-form-item 中的 validate 方法吗?,也就是 el-form 执行el-form-item 的校验方法,当你按下 校验按钮 的时候, trigger 为 空,但是 input 中的 trigger 就不一样了

input.vue

image.png

image.png

image.png

image.png

通过 inject 获取 el-form-item 实例,执行 validate 方法,这样就可以校验啦

总结

el-form 的校验简单来说,是通过 el-form 身上的 model 对象,匹配 el-form-itemprop 属性 即 fieldValue,使用 async-validator 中的实例 validate 拿着 el-form 中传递的 rules 进行 校验

const modelName = prop;
const fieldValue = model[prop]

const validator = new AsyncValidator({
   [modelName]: rules,
 })

也可以通过 el-input 中执行 el-form-itemvalidate 方法来执行校验

如果有错误,在 el-form-item 中显示

以上就是 el-form 中的关于表单验证总结