前端组件库的Form组件字段校验,到底是怎么做到的?

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心。

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

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

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

html 复制代码
<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 中组件之间传值的方式,我用一个简单的例子来给大家说明

html 复制代码
// 父组件
<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

html 复制代码
<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

html 复制代码
<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函数,去执行校验,并且决定展不展示错误提示

html 复制代码
<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>

小结

其实上面就是埋点库中,全局点击上报的一种解决方案,看似小问题,但是其实面试了这么多人,感觉只有很少一部分人能回答的比较好~

结语 & 加学习群 & 摸鱼群

我是林三心

  • 一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手;
  • 一个偏前端的全干工程师;
  • 一个不正经的掘金作者;
  • 一个逗比的B站up主;
  • 一个不帅的小红书博主;
  • 一个喜欢打铁的篮球菜鸟;
  • 一个喜欢历史的乏味少年;
  • 一个喜欢rap的五音不全弱鸡

如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 --> 摸鱼沸点

相关推荐
星辰引路-Lefan12 分钟前
[特殊字符] 开源一款基于 PaddleOCR 的纯离线 OCR 识别插件 | 支持身份证、银行卡、驾驶证识别
前端·开源·ocr
Cache技术分享14 分钟前
285. Java Stream API - 通过 Supplier 创建 Stream
前端·后端
阿基米东15 分钟前
从嵌入式到前端的探索之旅,分享 5 个开源 Web 小工具
前端·javascript·github
hxjhnct17 分钟前
响应式布局有哪些?
前端·html·css3
LYFlied18 分钟前
【每日算法】LeetCode215. 数组中的第K个最大元素
前端·算法
怎么就重名了18 分钟前
Kivy的KV语言总结
前端·javascript·html
jqq66618 分钟前
解析ElementPlus打包源码(四、打包主题)
前端·javascript·vue.js
代码猎人20 分钟前
类数组对象是什么,如何转化为数组
前端
duanyuehuan20 分钟前
js 解构赋值
开发语言·前端·javascript
进击的野人21 分钟前
Vue3 响应式系统深度解析:从原理到实践
前端·vue.js·前端框架