组件库实战 教你如何设计Web世界中的表单验证_web程序设计表单验证怎么做(1)

<template>
  <div class="container">
    <global-header :user="user"></global-header>
    <form action="">
      <div class="mb-3">
        <label for="exampleInputEmail1" class="form-label">邮箱地址</label>
        <input
 type="email" class="form-control" id="exampleEmail1"
 v-model="emailRef.val"
 @blur="validateEmail">
        <div class="form-text" v-if="emailRef.error">{{emailRef.message}}</div>
      </div>
      <div class="mb-3">
        <label for="exampleInputPassword1" class="form-label">密码</label>
        <input type="password" class="form-control" id="exampleInputPassword1">
      </div>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import ColumnList, { ColumnProps } from './components/ColumnList.vue'
import GlobalHeader, { UserProps } from './components/GlobalHeader.vue'
const currentUser: UserProps = {
 isLogin: true,
 name: 'Monday'
}
// 判断是否是邮箱的格式
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/
const testData: ColumnProps[] = [
 {
 id: 1,
 title: 'test1专栏',
 description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于ts的一些学习记录。'
 // avatar: 'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg'
 },
 {
 id: 2,
 title: 'test2专栏',
 description: '众所周知, js 是一门弱类型语言,并且规范较少。这就很容易导致在项目上线之前我们很难发现到它的错误,等到项目一上线,浑然不觉地,bug就UpUp了。于是,在过去的这两年,ts悄悄的崛起了。 本专栏将介绍关于ts的一些学习记录。',
 avatar: 'https://img0.baidu.com/it/u=3101694723,748884042&fm=26&fmt=auto&gp=0.jpg'
 }
]

export default defineComponent({
 name: 'App',
 components: {
 GlobalHeader
 },
 setup () {
 // 邮箱验证部分数据内容
 const emailRef = reactive({
 val: '',
 error: false,
 message: ''
 })
 // 验证邮箱逻辑
 const validateEmail = () => {
 // .trim 表示去掉两边空格
 // 当邮箱为空时
 if (emailRef.val.trim() === '') {
 emailRef.error = true
 emailRef.message = 'can not be empty'
 } 
 // 当邮箱不为空,但它不是有效的邮箱格式时
 else if (!emailReg.test(emailRef.val)) {
 emailRef.error = true
 emailRef.message = 'should be valid email'
 }
 }
 return {
 list: testData,
 user: currentUser,
 emailRef,
 validateEmail

 }
 }
})
</script>

现在,我们来看下具体的显示效果:

邮箱验证

好了,现在我们第一步就实现啦!那么接下来,我们是不是就应该来写 password 的逻辑了呢?

但是啊,如果按照上面这种方式来写的话,有小伙伴会不会觉得就有点重复操作了呢。一两个校验规则还好,如果我们遇到十几二十个呢?也一样每一个都这么写吗?

答案当然是否定的。那么下一步,我们就要对这个校验规则,来进行抽象。

3. 抽象验证规则

继续,我们现在要来抽象出用户名和密码的校验规则,让其可扩展性更强。具体形式如下:

<validate-input :rules="" />

interface RuleProp {
    type: 'required' | 'email' | 'range' | ...;
    message: string;
}
export type RulesProp = RuleProp[]

首先,我们要先把表单组件给抽离出来。那么现在,我们在 vue3 项目下的 src|components 下创建一个文件,命名为 ValidateInput.vue其具体代码如下:

<template>
  <div class="validate-input-container pb-3">
      <!-- 手动处理更新和发送事件 -->
      <!-- 使用可选 class,用于动态计算类名 -->
      <input type="text"
 class="form-control"
 :class="{'is-invalid': inputRef.error}"
 v-model="inputRef.val"
 @blur="validateInput"
 >
      <span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, PropType } from 'vue'
// 判断email的正则表达式
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/
// required表示必填值,email表示电子邮件的格式
// message用来展示当出现问题时提示的错误
interface RuleProp {
 type: 'required' | 'email';
 message: string;
 validator?: () => boolean;
}

export type RulesProp = RuleProp[]
export default defineComponent({
 name: 'ValidateInput',
 props: {
 // 用PropType来确定rules的类型,明确里面是RulesProp
 // 这里的rules数据将被父组件 App.vue 给进行动态绑定
 rules: Array as PropType<RulesProp>
 },
 setup(props, context) {
 // 输入框的数据
 const inputRef = reactive({
 val: '',
 error: false,
 message: ''
 })
 // 验证输入框
 const validateInput = () => {
 if (props.rules) {
 const allPassed = props.rules.every(rule => {
 let passed = true
 inputRef.message = rule.message
 switch (rule.type) {
 case 'required':
 passed = (inputRef.val.trim() !== '')
 break
 case 'email':
 passed = emailReg.test(inputRef.val)
 break
 default:
 break
 }
 return passed
 })
 inputRef.error = !allPassed
 }
 }
 return {
 inputRef,
 validateInput
 }
 }
})
</script>

<style>

</style>

之后我们将其在 App.vue 下进行注册。具体代码如下:

<template>
  <div class="container">
    <global-header :user="user"></global-header>
    <form action="">
      <div class="mb-3">
        <label class="form-label">邮箱地址</label>
        <validate-input :rules="emailRules"></validate-input>
      </div>
      <div class="mb-3">
        <label for="exampleInputEmail1" class="form-label">邮箱地址</label>
        <input
        type="email" class="form-control" id="exampleEmail1"
        v-model="emailRef.val"
        @blur="validateEmail">
        <div class="form-text" v-if="emailRef.error">{{emailRef.message}}</div>
      </div>
      <div class="mb-3">
        <label for="exampleInputPassword1" class="form-label">密码</label>
        <input type="password" class="form-control" id="exampleInputPassword1">
      </div>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import ValidateInput, { RulesProp } from './components/ValidateInput.vue'
import GlobalHeader, { UserProps } from './components/GlobalHeader.vue'
const currentUser: UserProps = {
  isLogin: true,
  name: 'Monday'
}
// 判断是否是邮箱的格式
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/

export default defineComponent({
  name: 'App',
  components: {
    GlobalHeader,
    ValidateInput
  },
  setup () {
    const emailRules: RulesProp = [
      { type: 'required', message: '电子邮箱不能为空' },
      { type: 'email', message: '请输入正确的电子邮箱格式' }
    ]
    const emailRef = reactive({
      val: '',
      error: false,
      message: ''
    })
    const validateEmail = () => {
      if (emailRef.val.trim() === '') {
        emailRef.error = true
        emailRef.message = 'can not be empty'
      } else if (!emailReg.test(emailRef.val)) {
        emailRef.error = true
        emailRef.message = 'should be valid email'
      }
    }
    return {
      user: currentUser,
      emailRef,
      validateEmail,
      emailRules
    }
  }
})
</script>

现在,我们在浏览器来看下它好不好用。具体效果如下:

抽象验证规则

大家可以看到,经过抽离后的验证规则,也正确的显示了最终的验证效果。课后呢,大家可以继续对 RuleProptype 进行扩展,比如多多加一个 range 功能等等。

到了这一步,我们对验证规则已经进行了简单的抽离。那接下来要做的事情就是,让父组件 App.vue 可以获取到子组件 ValidateInput.vueinput 框的值,对其进行数据绑定。

4. v-model

说到 input ,大家首先想到的可能是 v-model 。我们先来看下 vue2vue3 在双向绑定方面的区别:

<!-- vue2 原生组件 -->
<input v-model="val">
<input :value="val" @input="val = $event.target.value">

<!-- vue2自定义组件 -->
<my-component v-model="val" />
<my-component :value="val" @input="val = argument[0]" />

<!-- 非同寻常的表单元素 -->
<input type="checkbox" checked="val" @change="">

<!-- vue3 compile 以后的结果 -->
<my-component v-model="foo" />
h(Comp, {
	modelValue: foo,
	'onUpdate: modelValue': value => (foo = value)
})

对于 vue2 的双向绑定来说,主要有以下槽点:

  • 比较繁琐,需要新建一个 model 属性;
  • 不管如何,都只能支持一个 v-model ,没办法双向绑定多个值;
  • 写法比较让人难以理解。

基于以上 vue2 的几个槽点,现在我们用 vue3 来对这个组件的 input 值进行绑定,手动对其处理更新和事件发送。

首先我们在子组件 ValidateInput.vue 中进行处理,处理数据更新和事件发送。具体代码如下:

<template>
  <div class="validate-input-container pb-3">
      <input type="text"
 class="form-control"
 :class="{'is-invalid': inputRef.error}"
 :value="inputRef.val"
 @blur="validateInput"
 @input="updateValue"
 >
      <span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, PropType } from 'vue'
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/
interface RuleProp {
 type: 'required' | 'email';
 message: string;
 validator?: () => boolean;
}

export type RulesProp = RuleProp[]
export default defineComponent({
 name: 'ValidateInput',
 props: {
 rules: Array as PropType<RulesProp>,
 // 创建一个字符串类型的属性 modelValue
 modelValue: String
 },
 setup(props, context) {
 // 输入框的数据
 const inputRef = reactive({
 val: props.modelValue || '',
 error: false,
 message: ''
 })
 // KeyboardEvent 即键盘输入事件
 const updateValue = (e: KeyboardEvent) => {
 const targetValue = (e.target as HTMLInputElement).value
 inputRef.val = targetValue
 // 更新值时需要发送事件 update:modelValue
 context.emit('update:modelValue', targetValue)
 }
 const validateInput = () => {
 if (props.rules) {
 const allPassed = props.rules.every(rule => {
 let passed = true
 inputRef.message = rule.message
 switch (rule.type) {
 case 'required':
 passed = (inputRef.val.trim() !== '')
 break
 case 'email':
 passed = emailReg.test(inputRef.val)
 break
 default:
 break
 }
 return passed
 })
 inputRef.error = !allPassed
 }
 }
 return {
 inputRef,
 validateInput,
 updateValue
 }
 }
})
</script>

接下来,我们在 App.vue 中对其进行使用,具体代码如下:

<template>
  <div class="container">
    <global-header :user="user"></global-header>
    <form action="">
      <div class="mb-3">
        <label class="form-label">邮箱地址</label>
          <!-- 此处做修改 -->
        <validate-input :rules="emailRules" v-model="emailVal"></validate-input>
        {{emailVal}}
      </div>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import ValidateInput, { RulesProp } from './components/ValidateInput.vue'
import GlobalHeader, { UserProps } from './components/GlobalHeader.vue'
const currentUser: UserProps = {
 isLogin: true,
 name: 'Monday'
}
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/

export default defineComponent({
 name: 'App',
 components: {
 GlobalHeader,
 ValidateInput
 },
 setup () {
 // 创建emailVal的值
 const emailVal = ref('monday')
 const emailRules: RulesProp = [
 { type: 'required', message: '电子邮箱不能为空' },
 { type: 'email', message: '请输入正确的电子邮箱格式' }
 ]
 const emailRef = reactive({
 val: '',
 error: false,
 message: ''
 })
 const validateEmail = () => {
 if (emailRef.val.trim() === '') {
 emailRef.error = true
 emailRef.message = 'can not be empty'
 } else if (!emailReg.test(emailRef.val)) {
 emailRef.error = true
 emailRef.message = 'should be valid email'
 }
 }
 return {
 user: currentUser,
 emailRef,
 validateEmail,
 emailRules,
 emailVal
 }
 }
})
</script>

现在,我们来看下数据的值是否成功被绑定。具体效果如下:

v-model绑定值

大家可以看到,数据已经直接的被父组件给获取到并且也成功的绑定了。

5. 使用$attrs支持默认属性

上面我们基本上完成了整个组件的基本功能,现在,我们要来给它设置默认属性,也就是平常我们使用的 placeholder 。如果我们直接在 <validate-input /> 组件中绑定 placeholder ,那么默认地,会直接绑定到它的父组件上面去。因此呢,我们要禁止掉这种行为,让绑定后的 placeholder 给相应的放置在 input 元素上。

那这一块内容呢,涉及到的就是 vue3$attrs$attrs 可以让组件的根元素不继承 attribute ,并且可以手动决定这些 attribute 赋予给哪个元素。具体可查看官方文档:禁用 Attribute 继承

下面,我们来实现这一块的功能。

首先是子组件 ValidateInput.vue具体代码如下:

<template>
  <div class="validate-input-container pb-3">
      <!-- 手动处理更新和发送事件 -->
      <!-- 使用可选 class,用于动态计算类名 -->
      <input
 class="form-control"
 :class="{'is-invalid': inputRef.error}"
 :value="inputRef.val"
 @blur="validateInput"
 @input="updateValue"
 v-bind="$attrs"
 >
      <span v-if="inputRef.error" class="invalid-feedback">{{inputRef.message}}</span>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, PropType } from 'vue'
const emailReg = /^[a-zA-Z0-9.!#$%&’\*+/=?^\_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)\*$/
interface RuleProp {
 type: 'required' | 'email';
 message: string;
 validator?: () => boolean;
}

export type RulesProp = RuleProp[]
export default defineComponent({
 name: 'ValidateInput',
 props: {
 rules: Array as PropType<RulesProp>,
 modelValue: String
 },
 // 如果不希望组件的根元素继承attribute,那么可以在组件的选项中设置以下属性
 inheritAttrs: false,
 setup(props, context) {
 // 输入框的数据
 const inputRef = reactive({
 val: props.modelValue || '',
 error: false,
 message: ''
 })
 
 // $attrs包裹着传递给组件的attribute的键值对
 // console.log(context.attrs)
 
 // KeyboardEvent 即键盘输入事件
 const updateValue = (e: KeyboardEvent) => {
 const targetValue = (e.target as HTMLInputElement).value
 inputRef.val = targetValue
 context.emit('update:modelValue', targetValue)
 }
 // 验证输入框
 const validateInput = () => {
 if (props.rules) {
 const allPassed = props.rules.every(rule => {
 let passed = true
 inputRef.message = rule.message
 switch (rule.type) {
 case 'required':
 passed = (inputRef.val.trim() !== '')
 break
 case 'email':
 passed = emailReg.test(inputRef.val)
 break
 default:
 break
 }
 return passed
 })
 inputRef.error = !allPassed
 }
 }
 return {
 inputRef,
 validateInput,
 updateValue
 }
 }
})
</script>

之后是父组件 App.vue具体代码如下:

<template>
  <div class="container">
    <global-header :user="user"></global-header>
    <form action="">
      <div class="mb-3">
        <label class="form-label">邮箱地址</label>
        <!-- 需要让placeholder给添加到子组件的input元素上去,而不是添加到根元素上 -->
        <validate-input
 :rules="emailRules" v-model="emailVal"
 placeholder="请输入邮箱地址"
 type="text" />
      </div>
      <div class="mb-3">
        <label class="form-label">密码</label>
        <validate-input
 type="password"
 placeholder="请输入密码"
 :rules="passwordRules"
 v-model="passwordVal" />
      </div>
    </form>
  </div>
</template>

<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
import 'bootstrap/dist/css/bootstrap.min.css'
import ValidateInput, { RulesProp } from './components/ValidateInput.vue'
import GlobalHeader, { UserProps } from './components/GlobalHeader.vue'
const currentUser: UserProps = {
 isLogin: true,
 name: 'Monday'
}

export default defineComponent({
 name: 'App',
 components: {
 GlobalHeader,
 ValidateInput
 },
 setup () {
 const emailVal = ref('')
 const emailRules: RulesProp = [
 { type: 'required', message: '电子邮箱不能为空' },
 { type: 'email', message: '请输入正确的电子邮箱格式' }
 ]
 const passwordVal = ref('')
 const passwordRules: RulesProp = [
 { type: 'required', message: '密码不能为空' }
 ]
 return {
 user: currentUser,
 emailRules,
 emailVal,
 passwordVal,
 passwordRules
 }
 }
})
</script>


从上面的代码中我们可以了解到,通过 inheritAttrs: false$attrs ,实现了我们想要的效果。

我们现在来看下浏览器的显示结果:

使用$sttrs支持默认属性

💭二、验证表单ValidateForm

1. 组件需求分析

ValidateInput 除了基本的功能外,还可以进行功能扩散。比如,自定义校验、更多事件、更多不同的验证元素。

那么下面,我们要来设计整个验证表单,也就是 ValidateForm 组件,并且将 ValidateInput 给对应的使用到其中。

我们先来分析下这个 ValidateForm 都有哪些内容。先看下图:

ValidateForm分析

先看第一部分,我们首先把前面我们封装的 ValidateInput 给放进去,进行语义化包裹。

第二部分,我们可以对提交的按钮进行自定义化,比如提交的文字是怎么样的,提交的按钮又是怎么样的。

第三部分,我们需要有一个确定的事件来触发最后的结果,那么我们就在 ValidateForm 中,获取最后的结果。

第四部分,算是一个隐藏功能,也是这个组件的一个难点,即获取每个 ValidateForm 包裹下的 ValidateInput 的验证结果。

ok,到这里,我们就简单的对 ValidateForm 进行一个分析,那么下面我们将一步步的来对其进行代码设计。

2. 使用插槽 slot

首先,我们要先将提交按钮,做成动态的。一开始初始化一个值,之后呢,可以动态的改变按钮的文字和事件。那这个要用到的就是 vue 的中具名插槽

我们先在 vue3 项目下的 src|components 定义一个子组件,命名为 ValidateForm.vue 。现在我们来设计它,具体代码如下:

<template>
    <form class="validate-form-container">
        <slot name="default"></slot>
        <!-- @click.prevent 用来阻止事件的默认行为 -->
        <!-- 阻止表单提交,仅执行函数submitForm -->
        <div class="submit-area" @click.prevent="submitForm">
            <slot name="submit">
                <!-- 给插槽添加一个默认按钮 -->
                <button type="submit" class="btn btn-primary">提交</button>
            </slot>
        </div>


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值