组件化开发 | vue3+ts封装Input组件

4,276 阅读1分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

⏳前言

在组件化开发中,输入框组件在开发中也是一个常用的组件之一,但是每次单独为每一个input输入框添加样式、校验规则等有些繁琐,也不利于维护管理,那么这篇文章将用来记录学习封装带校验规则的ValidateInput输入框。实现的功能有:

  1. 支持自定义校验规则
  2. 校验效果及时反馈
  3. 父子组件数据双向绑定 另外一些可自定义的参数/api,例如占位符placeholderfocuschange可由需求扩展

自定义校验规则接口

这里以required必须项和email邮箱校验规则为例,并且需要一个校验失败的提示信息message

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

自定义ValidateInput组件

将输入的值动态绑定到inputRef.val,在输入框失焦blur时执行校验,并在校验失败后将反馈信息inputRef.message展示在提示框中

<template>
  <div class="validate-input-container pb-3">
    <input
      type="text"
      v-model="inputRef.val"
      class="form-control"
      @blur="validateInput"
    />
    <div v-if="inputRef.error" class="form-text">
      {{ inputRef.message }}
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, PropType, reactive } from 'vue'
interface RuleProp {
  type: 'required' | 'email';
  message: string;
}
const emailReg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/  // 验证邮箱
export type RulesProp = RuleProp[]

export default defineComponent({
  props: {
    rules: Array as PropType<RulesProp>,
    modelValue: String
  },
  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,
      updateValue
    }
  }
})
</script>

调用时传入校验规则

const emailRules: RulesProp = [
  { type: 'required', message: '邮箱不能为空' },
  { type: 'email', message: '请输入正确的邮箱格式' }
]

<validate-input :rules="emailRules" />

效果:

image.png

样式上与我们平时遇到的校验格式有很大出入,不过这些会在后面补上

组件数据双向绑定

到目前为止我们的input框有一个问题是:父组件不能拿到ValidateInput框内输入的内容,不能做到使用v-model双向绑定数据。我们都知道v-model的原理是通过v-bind绑定一个value值,结合@input事件动态改变绑定的value值实现数据双向绑定,那么我们在组件中我们也可以通过这个原理在组件间实现数据双向绑定

我们要知道在父组件中传入v-model的值,在子组件中是通过modelValue接收的

  1. ValidateInput通过modelValue接收父组件v-model绑定的值
// 子组件
props: {
  rules: Array as PropType<RulesProp>,
  modelValue: String;
}
  1. 给input框绑定value值和@input事件
<template>
  <div class="validate-input-container pb-3">
    <input
      type="text"
      class="form-control"
+     :value="inputRef.val"
+     @input="updateValue"
      :placeholder="placeholder"
      @blur="validateInput"
    />
  </div>
</template>

  1. 发射事件通知父组件修改v-model的值
const updateValue = (e: Event) => {
  const targetValue = (e.target as HTMLInputElement).value
  inputRef.val = targetValue
  context.emit('update:modelValue', targetValue)
}

整个过程我用下面这个图表示出来,其实就是一个父子组件通信的过程,这是因为子组件不能直接修改props值(props值只负责传递数据),而是需要通知父组件修改传过来的props值,进行数据更新,再拿到修改后的值。

image.png

自定义校验用户名的输入框

通过给ValidateInput加上校验成功/失败后的样式,来达到及时给用户反馈校验后信息的效果

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

<script lang="ts">
import { defineComponent, PropType, reactive } from 'vue'
interface RuleProp {
  type: 'required' | 'email' | 'username' | 'only'
  message: string
}
const emailReg = /^1[3|4|5|7|8][0-9]{9}$/
const usernameReg = /......./    // 这里写入用户名的正则表达式
export type RulesProp = RuleProp[]

export default defineComponent({
  props: {
    placeholder: String,
    rules: Array as PropType<RulesProp>
  },
  setup(props) {
    const emailRules: RulesProp = [
      { type: 'required', message: '用户名不能为空' },
      { type: 'username', message: '请输入正确格式的用户名' },
      { type: 'only', message: '用户名已存在' }
    ]
    const inputRef = reactive({
      val: '',
      success: true,
      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 'username':
              passed = usernameReg.test(inputRef.val)
              break
            case 'only':
              // 校验用户名是否存在
            default:
              break
          }
          return passed
        })
        inputRef.error = !allPassed
        inputRef.success = allPassed
      }
    }
    return {
      inputRef,
      validateInput
    }
  }
})
</script>

image.png