likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 19

前言

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 属性,这个属性必须存在于 rulesel-input 中使用 v-model 连接 model 中的某一个属性 在校验的时候,使用的 el-form 中的 validate 方法


起步

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

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

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

form.vue

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

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

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

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

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

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

🔥 el-form 辅助校验

1. doValidateField 起点

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

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

fields 进行判断

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

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

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

2. obtainValidateFields

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

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

不得不提一下 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

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

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

👇图 为 fields 数组

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

🚀 校验

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

过滤fileds
validate
validateField
doValidateField
obtainValidateFields
filterFields

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

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

这个是校验的代码,执行 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 去掉了

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

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

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

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

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

👨‍🚀 doValidate

重要! 重要! 重要!

是核心的校验方法,为了便于理解, rules 和 modelName 都以注释的形式放上去了, **使用AsyncValidator进行校验 rulesmodelName ** 来看看 element-ui 中 form 表单 的验证

AsyncValidator 是何许人也?

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

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

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

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

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

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

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

记得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 错误消息

在页面上使用

来看看 element-ui 中 form 表单 的验证 传递了一个具名的 error 插槽 ,同时把validateMessage 给抛出来

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

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

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

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

input.vue

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

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

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

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

通过 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 中的关于表单验证总结