表单验证在我们前端开发中是经常要用到的业务需求,在日常开发中会大量的使用!那么,我们每天都在使用的这种验证表单是如何封装实现的呢?今天我们就对这个组件的封装来进行研究学习。

首先看下elementui的效果

element ui chat前端 elementui ts_element ui chat前端


使用方法就不说了,可以仔细阅读使用文档

宇宙门: elementui官网

下面是我们今天要展示的自己封装的效果,是此次的重点!
相关的技术栈是vue3.0+ts+bootsrap(借用她的样式)

正常的情况下展示

element ui chat前端 elementui ts_vue_02


直接点击提交的时候,值为空所以会进行校验

element ui chat前端 elementui ts_element ui chat前端_03

输入完符合要求的内容,在点击提交的时候错误提示会消失

element ui chat前端 elementui ts_html_04

注意点一:vue3.0不支持 $on, $emit 的发布订阅 api,需要我们安装 mitt 这个插件进行事件通信传输数据, $emit 仍然是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序.
官方推荐使用第三方类库: mitt

cnpm i mitt -S / yarn add mitt

validateInput.vue

<template>
  <div class="form-group">
    <label :for="prop">{{ title }}</label>
    <!-- {{ $attrs }} -->
    <input
      class="form-control"
      :id="prop"
      :class="{ 'is-invalid': inputRef.error }"
      :value="inputRef.val"
      @blur="validateInput"
      @input="updateValue"
      v-bind="$attrs"
    />
    <div class="form-text invalid-feedback" v-if="inputRef.error">
      {{ inputRef.message }}
    </div>
  </div>
</template>

传参分析:

  • label for id 此处不用解释了吧,就是我们在点击文字title的时候自动聚焦到对应的输入框
  • v-bind= " $attrs " 父组件传递数据到子组件,vue2/3都是支持的。注意的地方是如果属性是以props的方式直接传递到子组件的话,$attrs是不包含这个属性的!
<script lang="ts">
import { defineComponent, onMounted, PropType, reactive, ref } from "vue";
//这个emitter 是在 ValidateForm暴露出来的,我们引入进行事件的emit
import { emitter } from "./ValidateForm.vue";
const reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;
interface RuleProp {
  type: "required" | "email" | "password";
  message: string;
}
//这是个type定义的校验规则
export type RulesProp = RuleProp[];
export default defineComponent({
  inheritAttrs: false, //$attrs属性不在子组件的dom上展示就设置为false
  props: {
    rules: {
      type: Array as PropType<RulesProp>,
    },
    title: {
      type: String,
    },
    prop: {
      type: String,
    },
    modelValue: {
      type: String,
      default: "",
    },
  },
  setup(context, props) {
    const emailVal = ref("");
    const inputRef = reactive({
      val: context.modelValue,
      error: false,
      message: "",
    });
    //校验每个输入框的方法,统一在这里进行判断true or false
    const validateInput = () => {
      if (context.rules) {
      //setup里面不能使用this,可以用context获取上下文,与2.0this类似        
      const allPassed = context.rules.every((item) => {
          let passed = true;
          inputRef.message = item.message;
          switch (item.type) {
            case "required":
              passed = inputRef.val.trim() != "";
              break;
            case "email":
              passed = reg.test(inputRef.val);
              break;
            case "password":
              console.log(inputRef.val, "inputRef.val");
              passed = inputRef.val.length > 6;
            default:
              break;
          }
          return passed;
        });
        inputRef.error = !allPassed;
        return allPassed;
      }
      return true;
    };
    const updateValue = (e: KeyboardEvent) => {
      const targetValue = (e.target as HTMLInputElement).value;
      inputRef.val = targetValue;
      props.emit("update:modelValue", targetValue);
    };
    onMounted(() => {
      //触发submit提交事件,把validateInput 方法传递给ValidateForm
      emitter.emit("submit", validateInput);
    });
    return { inputRef, validateInput, updateValue };
  },
  mounted() {},
});
</script>

ValidateForm.vue

<template>
  <div>
    <form class="validate-form-container">
      <slot></slot>
      <div class="submit-area" @click.prevent="submitForm">
      //具名插槽,设置了默认值,我们不设置自定义的按钮的时候就显示这个默认的按钮
        <slot name="submit">
          <button type="submit" class="btn btn-primary">
            提交
          </button>
        </slot>
      </div>
    </form>
  </div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted } from "vue";
//引入官方推荐第三方的插件mitt
import mitt from "mitt";
//极乐门:[mitt](https://www.npmjs.com/package/mitt)
type Events = {
  submit: ValidateFunc; //事件名: 类型
};
type ValidateFunc = () => boolean; // 返回值为boolean的校验

export const emitter = mitt<Events>();
export default defineComponent({
  setup(context, props) {
     //组件被多次复用,触发多次,我们要拿到所有的组件的验证结果
    let funcArr: ValidateFunc[] = [];
    //此处进行遍历,对validateInput 传递出来的每个validateInput方法执行
    const submitForm = () => {
      const result = funcArr.map((item) => item()).every((result) => result);
      //然后触发调用ValidateForm组件上的自定义事件@form-submit="onFormSubmit",遍历校验的值传递出去
      props.emit("form-submit", result);
    };
    //每个input都会触发一个validateInput,把它保存到一个数组里面
    const callback = (func: ValidateFunc) => {
      // console.log(func(), "test1");  
      funcArr.push(func);
    };
    emitter.on("submit", callback);
    onUnmounted(() => {
    //卸载的时候销毁这个监听,内存开销节省
      emitter.off("submit", callback);
      //事件数组值为空
      funcArr = [];
    });
    return { submitForm };
  },
  emits: ["form-submit"],
});
</script>

上面的代码需要注意的是,文档对ts的特别说明,不想被类型验证搞死的要仔细阅读这里,不然红波浪会一直缠着你

element ui chat前端 elementui ts_element ui chat前端_05


Home.vue

<validate-form @form-submit="onFormSubmit">
      <validate-input
        :rules="emailRules"
        :title="'Email Address'"
        :prop="'email'"
        type="email"
        v-model="modelValue"
        placeholder="请输入邮箱地址"
      ></validate-input>
      <validate-input
        :rules="pwRules"
        :title="'Password'"
        :prop="'text'"
        type="password"
        v-model="passwordValue"
        placeholder="请输入密码"
      ></validate-input>
      //具名插槽的简写方式1
      <template #submit>
        <button name="submit" type="submit" class="btn btn-danger">
          提 交
        </button>
      </template>
    </validate-form>
    //方式2,1或者2按个人喜欢进行选取吧
      <template v-slot:submit>
        <button name="submit" type="submit" class="btn btn-danger">
          提 交
        </button>
      </template>
    </validate-form>

js部分

<script lang="ts">
import { defineComponent, reactive, ref } from "vue";
import ValidateInput from "../components/ValidateInput.vue";
import ValidateForm from "../components/ValidateForm.vue";
import GlobalHeader, { UserProps } from "../components/GlobalHeader.vue";
import ColumnList, { ColumnProps } from "../components/ColumntList.vue";

const user: UserProps = {
  isLogin: true,
  name: "Gaofeng",
  id: 0,
};
const reg = /^([a-zA-Z]|[0-9])(\w|\-)+@[a-zA-Z0-9]+\.([a-zA-Z]{2,4})$/;

export default defineComponent({
  name: "Home",
  components: { ColumnList, GlobalHeader, ValidateInput, ValidateForm },
  setup() {
    const emailRef = reactive({
      val: "",
      error: false,
      message: "",
    });
    const emailValidate = () => {
      if (emailRef.val.trim() === "") {
        emailRef.error = true;
        emailRef.message = "can not be empty";
      } else if (!reg.test(emailRef.val)) {
        emailRef.error = true;
        emailRef.message = "format is not correct";
      } else {
        emailRef.error = false;
        emailRef.message = "";
      }
    };
    const emailRules = [
      {
        type: "required",
        message: "电子邮箱地址不能为空",
      },
      {
        type: "email",
        message: "请输入正确的电子邮箱地址",
      },
    ];
    const pwRules = [
      {
        type: "required",
        message: "密码不能为空",
      },
      {
        type: "password",
        message: "密码长度不能小于6位",
      },
    ];
    const modelValue = ref("");
    const passwordValue = ref("");
    const inputRef = ref(null);
    const onFormSubmit = (value: boolean) => {
      //此处就是接受返回的验证值,是否通过验证
      console.log(value, "dddd");
    };
    return {
      lists,
      user,
      emailRef,
      emailValidate,
      emailRules,
      modelValue,
      inputRef,
      pwRules,
      onFormSubmit,
      passwordValue,
    };
  },
});
</script>

true 通过

element ui chat前端 elementui ts_js_06

false 未通过

element ui chat前端 elementui ts_js_07


到此这个校验的表单组件就封装完成了,相比vue2.0的分装,主要ts的格式校验比较繁琐,耐心的去查文档吧!打完收工~~~