不试着自己封装一个element-UI吗?vue3+ts+tsx+vite封装一个表单ui组件(与element-ui,ant等相同效果)

导读:本篇文章讲解 不试着自己封装一个element-UI吗?vue3+ts+tsx+vite封装一个表单ui组件(与element-ui,ant等相同效果),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

在这里插入图片描述

表单的复杂度在多种多样的数据收集方式,我们一开始先从最简单的表单容器Form入手,

后续我们再完成几个代表性表单元素组件,这个过程我们能够充分的应用Vue3中各种组件通信技术和掌握多种用户交互实现。

Form需求分析

表单就像我们进入银行营业厅大堂经理让我们填写的单子一样,包含了想要收集的信息,通常是输入项或者列表选项,还有标签用来说明。在组件库中的表单会更复杂一些,因为我们没有大堂经理,所以需要自己实现“表单校验”、“信息反馈”等功能。

通常我们会提出一个表单组件Form,它是最外层容器,用户通过它:

  • 包裹所有表单项,传入整个数据模型
  • 设置表单布局方式:水平或垂直
  • 表单数据校验

期待的使用方式

<s-form layout="vertical" :data="formModel">...</s-form>

实现Form

  • 表单KForm
    • 容器组件:插槽 – slot
    • 输⼊:数据 – model,校验规则 – rules
    • 方法:校验 – validate

Form代码编写

期望的使用方式:

<KForm :model="model" :rules="rules" ref="lf">...</KForm>

定义Form,Form.vue:

<template>
  <form class="kform"><slot></slot></form>
</template>
<script setup lang="ts">
import { PropType } from "vue";
import { Rules } from "async-validator";

// 输入属性
const props = defineProps({
  model: { type: Object, required: true },
  rules: { type: Object as PropType<Rules> },
});
</script>

安装async-validator

使用Form

调用Form,App.vue:

<template>
  <KForm :model="model" ref="loginForm">form</KForm>
</template>

<script setup lang="ts">
import KForm from "./components/Form.vue";
import { ref, reactive } from "vue";

const model = reactive({
  username: "aaa",
});

const rules = reactive({
  username: [{ required: true, message: "用户名为必填项" }],
});

// 需要定义Form约束loginForm类型
const loginForm = ref<Form>();
</script>

定义一个Form类型,components/types.ts

export type Form = {
  // 未来这里可以定义Form接口
};

表单项 FormItem

需求分析

表单项FormItem是数据项的抽象,用户通过它包裹表单控件,定义标签,做校验等任务。

  • 表单KFormItem
    • 容器组件:插槽 – slot
    • 输⼊:标签 – label,字段名 – prop
    • 方法:校验 – validate

期待的使用方式

<s-form layout="vertical" :data="formModel">
  <d-form-item field="name">
    <d-form-label>Name</d-form-label>
    <d-form-control>
      <d-input v-model="formModel.name" />
    </d-form-control>
  </d-form-item>
  <d-form-item field="description">
    <d-form-label>Description</d-form-label>
    <d-form-control>
      <d-textarea v-model:value="formModel.description" />
    </d-form-control>
  </d-form-item>
</d-form>

代码编写

期待的用法:

<KFormItem label="username" prop="username">
  ...
</KFormItem>

定义表单项,KFormItem.vue

<template>
  <div>
    <label v-if="label">{{ label }}</label>
    <slot></slot>
    <p v-if="error">{{ error }}</p>
  </div>
</template>
<script setup lang="ts">
import { ref, withDefaults } from "vue";

// 定义输入属性类型
interface Props {
  label?: string;
  prop?: string;
}

// 定义属性默认值
const props = withDefaults(defineProps<Props>(), { label: "", prop: "" });

// 定义校验错误信息
const error = ref("");
</script>

使用FormItem

使用FormItem,App.vue

<KForm :model="model" ref="loginForm">
  <KFormItem label="username" prop="username">username</KFormItem>
</KForm>
import KFormItem from "./components/FormItem.vue";

输入框 Input

Input需求分析

收集用户输入信息即可,输入框组件需要实现一个双绑,后续可能还需要状态反馈、图标等功能。

  • 表单KInput
    • 双绑
    • 触发校验

Input代码编写

期待用法:

<KInput v-model="model.username"></KInput>

定义输入框,Input.vue:

<template>
  <div>
    <input :value="modelValue" @input="onInput" />
  </div>
</template>
<script setup lang="ts">
defineProps({
  modelValue: {
    type: String,
    default: "",
  },
});

const emit = defineEmits<{
  (e: "update:model-value", value: string): void;
}>();

function onInput(e: Event) {
  const inp = e.target as HTMLInputElement;
  emit('update:model-value', inp.value)
}
</script>

使用Input

使用如下,App.vue

<template>
  <KForm :model="model" ref="lf">
    <KFormItem label="username">
      <KInput v-model="model.username"></KInput>
    </KFormItem>
  </KForm>
</template>  
import KInput from "./components/Input.vue";

实现数据校验

触发校验

这里需要定义一个事件派发器,便于KInput和KFormItem之间通信,

composables/useEmitter.ts:

import mitt from "mitt";

export type Events = {
  validate: undefined;
};

export const emitter = mitt<Events>();

KInput派发事件,触发KFormItem校验,KInput.vue

import { emitter } from "../composables/useEmitter";

function onInput(e: Event) {
  // ...
  
  // 派发校验事件
  emitter.emit("validate");
}

KFormItem中监听校验事件, KFormItem.vue

import { onMounted } from "vue";
import { emitter } from "../composables/useEmitter";

// 挂载成功,监听校验事件
onMounted(() => {
  // 如果需要被校验则在form中注册自己
  if (props.prop) {
    emitter.on("validate", () => {
      validate();
    });
  }
});

执行校验

这里有两个任务:

  • FormItem单项校验
  • Form全局校验

校验依赖async-validator,安装一下:

npm i async-validator -S

FormItem单项校验

首先Form中需要将数据提供给FormItem使用,类型支持需要定义一个FormData类型和key,types.ts

import { InjectionKey } from "vue";
import { Rules } from "async-validator";

export type FormData = {
  model: Record<string, any>;
  rules?: Rules;
};

export const key: InjectionKey<FormData> = Symbol("form-data");

Form中去提供这些数据,Form.vue

import { key } from "./types";
import { provide } from "vue";

provide(key, {
  model: props.model,
  rules: props.rules,
});

FormItem需要注入表单数据、监听校验事件并执行校验,FormItem.vue

import { inject } from "vue"
import { key } from "./types";
import Schema, { Rules } from "async-validator";

// 注入数据模型和校验规则,这里需要在types.ts中定义key
const formData = inject(key);
function validate() {
  if (formData?.rules === undefined) {
    return Promise.resolve({result: true})
  }
  
  // 获取校验规则
  const rules = formData.rules[props.prop];
  // 获取校验值
  const value = formData.model[props.prop];

  // 校验描述对象
  const descriptor = { [props.prop]: rules } as Rules;
  // 创建校验器
  const schema = new Schema(descriptor);
  // 返回Promise,没有触发catch就说明验证通过
  return schema.validate({ [props.prop]: value }, (errors) => {
    if (errors) {
      // 将错误信息显示
      error.value = errors[0].message || "校验错误";
    } else {
      // 校验通过
      error.value = "";
    }
  });
}

Form中全局校验

Form需要知道都有哪些FormItem需要校验,所以首先需要校验的FormItem会通知Form添加自己。

后续只要循环执行所有FormItem的校验方法即可知道最终校验结果。

首先定义FormItem类型,types.ts

import { Values } from "async-validator";

// FormItem对外暴露validate接口,返回校验
export type FormItem = {
  validate: () => Promise<Values>;
};

定义addFormItem事件,useEmitter.ts

import { FormItem } from './../components/types';

export type Events = {
  // 加一个addFormItem事件
  addFormItem: FormItem;
};

定义对外暴露的接口并通知Form添加自己,FormItem.vue

// 定义对外暴露的接口
const o: FormItem = { validate };
defineExpose(o);

onMounted(() => {
  if (props.prop) {
    // 通知Form添加自己
    emitter.emit("addFormItem", o);
  }
});

表单中监听addFormItem事件,并对外暴露validate方法,Form.vue

import { ref } from "vue";
import { FormItem } from "./types";
import { emitter } from "../composables/useEmitter";

// 添加需要校验的表单项
const items = ref<FormItem[]>([]);
emitter.on("addFormItem", (item) => {
  items.value.push(item);
});

// 对外暴露validate方法
defineExpose({
  validate(cb: (isValid: boolean) => void) {
    // 调⽤所有FormItem的validate⽅法并得到结果
    const tasks = items.value.map((item) => item.validate());
    // 所有任务全部成功则校验通过
    Promise.all(tasks)
      .then(() => cb(true))
      .catch(() => cb(false));
  },
});

验证全局校验

定义Form类型,types.ts

export type Form = {
  validate: (cb: (isValid: boolean) => void) => void;
};

调用校验,App.vue

function onLogin() {
  if (!loginForm.value) return;
  loginForm.value.validate((valid: boolean) => {
    console.log(valid);
  });
}
<KFormItem><button @click.prevent="onLogin">登录</button></KFormItem>

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/79701.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!