组件库中Form表单校验,到底是怎么做到的?带你实现一遍~

作者: jie 分类: Vue 发布时间: 2023-09-23 15:17

我们平时开发的时候,肯定都会接触到表单吧,那大家有没有想过一件事情,为啥你每次输入的时候,就能马上触发到表单的校验呢?

有些兄弟就会好奇,这些个 input 框改变的时候,是怎么能触发到顶部 form 的校验的呢?

我们使用表单时,代码大概是这样的

<template>
  <div style="margin-left: 300px; margin-top: 300px">
    <test-form :rules="rules" :data="formData">
      <test-form-item field="name">
        <test-input v-model="formData.name" />
      </test-form-item>
    </test-form>
  </div>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'
import TestForm from './Form.vue';
import TestFormItem from './Form-Item.vue'
import TestInput from './Input.vue';

// 规则
const rules = {
  name: {
    required: true
  }
}
// 数据
const formData = reactive({
  name: '林三心'
})
</script>

其实校验的核心功能就是三个东西

  • 表单规则:在 test-form 中
  • 表单字段:在 test-form-item 中
  • 表单值:在 test-input 中

我们可以通过表单字段去获取到实时的表单值,接着去表单规则中去匹配,就可以获取到校验结果了~

实现效果

看一下,本文章想要实现的效果如下,别看这个功能只是组件库中一个很简单的功能,但是这几天面试下来,发现面试者很少人能回答得上来~

Provide & Inject

在讲实现之前我们先来讲讲 Provide & Inject,他是一种 vue3 中组件之间传值的方式,我用一个简单的例子来给大家说明

// 父组件
<script lang='ts' setup>
  import { provide } from 'vue'
  
  provide('form', {
    rules: {
      name: { required: true }
    }
  })
</script>

// 子组件
<script lang='ts' setup>
  import { provide, inject } from 'vue'
  
  const formInject = inject('form')
  console.log(formInject)
  // { rules: {
  //      name: { required: true }
  //      } }
  
  provide('form-item', {
    field: 'name'
  })
</script>

// 孙子组件
<script lang='ts' setup>
  import { inject } from 'vue'
  
  const formInject = inject('form')
  const formItemInject = inject('form-item')
  
  console.log(formInject)
  // { rules: {
  //      name: { required: true }
  //  } }
  
  console.log(formInject)
  //{
  //  field: 'name'
  //}
  
</script>

基本实现原理

我们上面说了,实现表单校验的三个重要因素是

  • 表单规则:在 test-form 中
  • 表单字段:在 test-form-item 中
  • 表单值:在 test-input 中

只有将这三个东西结合起来,才能做到校验,我画了个图,大家可以看看

大致分为几步:

  • 1、From 将 rules、validate函数 传给 From-Item
  • 2、From-Item 将 field、onChange函数 传给 Input
  • 3、Input的 value 改变时触发 validate、onChange函数,去执行校验,并且决定展不展示错误提示

具体实现

From

Form 要做到 将 rules、validate函数 传给 From-Item

<template>
  <form>
    <slot></slot>
  </form>
</template>

<script lang="ts" setup>
import { provide, reactive } from 'vue'
const props = defineProps<{ rules: any; data: any }>();
// 字段有多个,所以需要维护一个错误表
const errorMap = reactive<any>({})

// 校验函数
const validateFn = (field: string): Promise<void> => {
  return new Promise((resolve, reject) => {
    const { rules, data } = props;
    const ruleItem = rules[field]
    const dataItem = data[field]
    if (ruleItem.required && dataItem === '') {
      return reject()
    }
    resolve()
  });
};

// 执行校验
const validate = (field: string) => {
  validateFn(field).then(() => {
    errorMap[field] = false
  }).catch(() => {
    errorMap[field] = true
  })
}


// 注入
provide('test-form', {
  validate,
  getErrorMap: () => errorMap
})
</script>

From-Item

Form-Item 要做到 将 field、onChange函数 传给 Input

<template>
  <div>
    <slot></slot>
    <div style="color: red" v-if="data.showError">字段必填</div>
  </div>
</template>

<script lang="ts" setup>
import { provide, inject, reactive } from 'vue';
const props = defineProps<{ field: string }>();
const testForm = inject<{ validate: (field: string) => Promise<any>; getErrorMap: any }>('test-form');
const data = reactive({
  showError: false,
});

// value change时执行
const onChange = () => {
  setTimeout(() => {
    if (testForm) {
      const showError = testForm.getErrorMap()[props.field]
      // 决定展示不展示错误提示
      data.showError =showError
    }
  })
}

// 注入
provide('test-form-item', {
  getField: () => props.field,
  onChange
});
</script>

Input

Input 要做到 value 改变时触发 validate、onChange函数,去执行校验,并且决定展不展示错误提示

<template>
  <input @input="onChange" :value="data.inputValue" />
</template>

<script lang="ts" setup>
import { reactive, watch, inject } from 'vue';

const props = defineProps<{ modelValue: string }>();
const emits = defineEmits(['update:modelValue']);

// 接收注入
const testForm = inject<{ validate: (field: string) => Promise<any>; getErrorMap: any }>(
  'test-form',
);
const testFormItem = inject<{ getField: () => string; onChange: () => void }>('test-form-item')

// 内部维护 value
const data = reactive({
  inputValue: props.modelValue,
});

watch(
  () => props.modelValue,
  v => {
    data.inputValue = v;
  },
);

// value change 时,执行 validate、onChange
const onChange = (e: Event) => {
  emits('update:modelValue', (e.target as HTMLInputElement).value);
  if (testForm && testFormItem) {
    testForm.validate(testFormItem.getField())
    testFormItem.onChange()
  }
};
</script>

发表回复