表单的复杂度在多种多样的数据收集方式,我们一开始先从最简单的表单容器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