用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能

在 Vue3 中结合 TypeScript 封装表单绑定方案时,需要综合考虑类型安全、功能扩展性和开发体验。以下是一个包含防抖功能、支持多种表单控件、具备完整类型推导的封装方案,全文约 2300 字:

方案设计思路

  1. 组合式函数封装:使用 Vue3 的 setup 语法和 Composition API
  2. 类型泛型支持:通过 TypeScript 泛型实现表单数据的强类型约束
  3. 防抖功能集成:内置可配置的防抖逻辑,支持异步操作
  4. 多控件支持:统一处理 input/textarea/select/checkbox 等常见表单元素
  5. 验证机制扩展点:预留自定义验证逻辑的接入接口

完整实现代码

import { ref, watch, computed, Ref } from 'vue';
import type { MaybeRefOrGetter, ComputedRef } from 'vue';// 防抖函数实现(兼容异步)
function debounce<T extends (...args: any[]) => any>(func: T,wait: number,immediate?: boolean
): (...args: Parameters<T>) => void {let timeout: ReturnType<typeof setTimeout> | null = null;return (...args: Parameters<T>) => {const callNow = immediate && !timeout;const later = () => {timeout = null;if (!immediate) func.apply(this, args);};clearTimeout(timeout!);timeout = setTimeout(later, wait);if (callNow) func.apply(this, args);};
}// 表单字段配置接口
interface FormFieldConfig<T = any> {value?: T;debounce?: number;validator?: (value: T) => string | boolean;transform?: (value: T) => any;
}// 表单状态管理类
class FormState<T extends Record<string, any>> {private _rawValues: T;private fields: Record<keyof T, FormFieldConfig>;private _errors: Ref<Record<string, string>> = ref({});private _isValid: ComputedRef<boolean> = computed(() => Object.values(this._errors.value).every(msg => !msg));constructor(initialValues: T, fieldConfigs: Record<keyof T, FormFieldConfig> = {}) {this._rawValues = initialValues;this.fields = fieldConfigs;this.initFields();}private initFields() {Object.keys(this._rawValues).forEach(key => {if (this.fields[key as keyof T]?.debounce) {const debounceTime = this.fields[key as keyof T]!.debounce!;const originalSetter = (val: any) => {this._rawValues[key as keyof T] = val;};this._rawValues[key as keyof T] = new Proxy(this._rawValues[key as keyof T], {set: debounce((_, prop, newValue) => {originalSetter(newValue);}, debounceTime)});}});}public get values(): T {return this._rawValues;}public get errors(): Record<string, string> {return this._errors.value;}public get isValid(): boolean {return this._isValid.value;}public validateField(field: keyof T): boolean {const config = this.fields[field];const value = this._rawValues[field];if (config?.validator) {const result = config.validator(value);this._errors.value[field as string] = typeof result === 'string' ? result : result ? '' : 'Invalid value';}return !this._errors.value[field as string];}public validateAll(): boolean {let isValid = true;Object.keys(this._rawValues).forEach(key => {if (!this.validateField(key as keyof T)) isValid = false;});return isValid;}public reset(): void {this._rawValues = Object.keys(this._rawValues).reduce((acc, key) => ({ ...acc, [key]: this.fields[key as keyof T]?.value || '' }),{} as T);this._errors.value = {};}
}// 组合式函数
export function useForm<T extends Record<string, any>>(initialValues: T,fieldConfigs?: Record<keyof T, FormFieldConfig>
) {const formState = new FormState(initialValues, fieldConfigs);// 创建响应式表单值const formValues = computed({get: () => formState.values,set: (newValues) => {Object.assign(formState.values, newValues);}});// 字段绑定函数function useField<K extends keyof T>(field: K) {const value = computed({get: () => formValues.value[field],set: (newValue) => {formValues.value[field] = newValue;}});const error = computed(() => formState.errors[field as string]);return {value,error,validate: () => formState.validateField(field)};}// 表单提交处理async function handleSubmit<R = any>(submitFn: (values: T) => Promise<R>,options?: { validate?: boolean }): Promise<R | null> {if (options?.validate && !formState.validateAll()) return null;try {const result = await submitFn(formValues.value);formState.reset();return result;} catch (e) {console.error('Form submission error:', e);return null;}}return {values: formValues,errors: computed(() => formState.errors),isValid: computed(() => formState.isValid),useField,validate: () => formState.validateAll(),reset: () => formState.reset(),submit: handleSubmit};
}// 类型导出
export type FormFieldConfig = FormFieldConfig;
export type FormStateType<T> = FormState<T>;

使用示例

<script setup lang="ts">
import { useForm } from '@/composables/useForm';interface LoginForm {email: string;password: string;remember: boolean;
}const formConfig = {email: {debounce: 500,validator: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? true : 'Please enter a valid email address'},password: {debounce: 300,validator: (value: string) => value.length >= 8 ? true : 'Password must be at least 8 characters'}
};const { values, errors, isValid, useField, submit } = useForm<LoginForm>({email: '',password: '',remember: false}, formConfig);const email = useField('email');
const password = useField('password');
const remember = useField('remember');const handleSubmit = submit(async (values) => {console.log('Form submitted:', values);// 这里调用APIreturn { success: true };
});
</script><template><form @submit.prevent="handleSubmit"><div><label>Email</label><input v-model="email.value" type="email"><div v-if="email.error" class="error">{{ email.error }}</div></div><div><label>Password</label><input v-model="password.value" type="password"><div v-if="password.error" class="error">{{ password.error }}</div></div><div><input v-model="remember.value" type="checkbox" id="remember"><label for="remember">Remember me</label></div><button type="submit" :disabled="!isValid">Submit</button></form>
</template>

方案特点分析

  1. 强类型支持

    • 使用 TypeScript 泛型确保表单数据的类型安全
    • 字段配置与验证函数类型约束
    • 组合式函数返回值类型明确
  2. 防抖功能

    • 通过 Proxy 实现动态属性拦截
    • 可配置的防抖时间(字段级配置)
    • 支持异步验证场景
  3. 验证系统

    • 内置验证器接口
    • 字段级错误收集
    • 表单整体有效性状态
    • 支持自定义验证逻辑
  4. 扩展性设计

    • 表单状态管理类封装核心逻辑
    • 组合式函数暴露友好API
    • 预留 transform 等数据处理钩子
    • 支持异步提交处理
  5. 开发体验优化

    • 符合 Vue 响应式设计哲学
    • 字段绑定自动推导类型
    • 错误信息自动收集展示
    • 表单提交自动重置

性能优化点

  1. 防抖内存管理:使用 Proxy 替代直接属性替换,避免重复创建对象
  2. 计算属性缓存:合理使用 computed 减少不必要计算
  3. 批量更新:表单提交时使用 Object.assign 批量更新
  4. 错误状态隔离:每个字段错误状态独立存储,避免全局遍历

扩展方向建议

  1. 集成第三方验证库:如 Vuelidate、Yup 等
  2. 添加字段掩码:支持密码显示切换、敏感信息脱敏
  3. 表单版本管理:实现表单状态的历史记录
  4. 可视化校验提示:集成 UI 框架的表单验证样式
  5. 国际化支持:多语言错误提示

本方案通过 TypeScript 泛型和 Vue3 组合式 API 的结合,实现了类型安全、功能丰富的表单绑定解决方案。防抖功能的集成既减少了不必要的验证触发,又提升了表单的响应性能,同时保持了代码的可维护性和扩展性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/922040.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922040.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中悦大华通过订单日记实现流程重构之路

一、客户背景 安徽中悦大华高速流体机械有限公司&#xff0c;成立于2023年&#xff0c;位于安徽省宣城市&#xff0c;是一家以从事电子设备制造为主的企业&#xff0c;在多年的商业经营中已成为业界翘楚。 在业务不断壮大的过程中&#xff0c;面临生产协作效率低&#xff0c;库…

【Springboot】介绍启动类和启动过程

【Springboot】介绍启动类和启动过程【一】Spring Boot 启动类的注解【1】核心注解&#xff1a;SpringBootApplication&#xff08;1&#xff09;​SpringBootConfiguration​&#xff1a;Spring容器会从该类中加载Bean定义&#xff08;2&#xff09;​EnableAutoConfiguration…

Gears实测室:第一期·音游跨设备性能表现与工具价值实践

在音游品类中&#xff0c;《跳舞的线》以 “音乐与操作节奏深度绑定” 的玩法特性&#xff0c;对设备性能提出了特殊要求 —— 稳定的帧率与低延迟的渲染响应&#xff0c;直接影响玩家对音符时机的判断&#xff0c;一旦出现卡顿或帧波动&#xff0c;易导致操作失误&#xff0c;…

格式刷+快捷键:Excel和WPS表格隔行填充颜色超方便

有时候我们会对Excel或WPS表格的数据区域每隔一行填充一个底纹&#xff0c;便于阅读和查看。可以使用条件格式搭配公式实现&#xff0c;也可以手动设置。通常手动设置的时候是先设置一行&#xff0c;然后再双击格式刷应用。可以有更快的方式&#xff1a;先设置一行底纹&#xf…

将现有Spring Boot项目作为模块导入到另一个Spring Boot项目

将现有Spring Boot项目作为模块导入到另一个Spring Boot项目的操作步骤如下&#xff1a;‌项目结构调整‌将待导入的项目文件夹复制到主项目的根目录下修改子模块目录名保持命名规范&#xff08;如ms-xxx格式&#xff09;‌父POM配置‌在主项目的pom.xml中添加<modules>声…

激光频率梳 3D 轮廓测量 - 铣刀刀片的刀口钝化值 R 的测量

一、引言铣刀刀片的刀口钝化值 R 是影响切削性能的关键参数&#xff0c;其精度直接关系到工件表面质量与刀具寿命。传统测量方法在面对微米级钝化圆角时存在分辨率不足、接触式测量易损伤刃口等问题。激光频率梳 3D 轮廓测量技术凭借飞秒级时频基准与亚微米级测量精度&#xff…

3-10〔OSCP ◈ 研记〕❘ WEB应用攻击▸XSS攻击理论基础

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

《嵌入式硬件(四):温度传感器DS1820》

一、DS1820的引脚DS1820单总线数字温度计&#xff1a;异步串行半双工特性&#xff1a;1&#xff09;独特的单线接口&#xff0c;只需 1 个接口引脚即可通信2&#xff09;多点&#xff08;multidrop&#xff09;能力使分布式温度检测应用得以简化3&#xff09;不需要外部元件4&a…

langchain 输出解析器 Output Parser

示例中使用的公共代码&#xff1a; from langchain_deepseek import ChatDeepSeek chat ChatDeepSeek(model"deepseek-chat",temperature0,api_keyAPI_KEY, )使用方法&#xff1a; 引入解析器实例化解析器调用解析器的get_format_instructions()获得提示词&#xff…

LeetCode算法日记 - Day 37: 验证栈序列、N叉树的层序遍历

目录 1. 验证栈序列 1.1 题目解析 1.2 解法 1.3 代码实现 2. N叉树的层序遍历 2.1 题目解析 2.2 解法 2.3 代码实现 1. 验证栈序列 https://leetcode.cn/problems/validate-stack-sequences/description/ 给定 pushed 和 popped 两个序列&#xff0c;每个序列中的 值…

金融数据库--3Baostock

一、 Baostock 是什么&#xff1f;Baostock&#xff08;宝硕股票&#xff09;是一个免费、开源的证券数据平台&#xff08;SDK&#xff09;&#xff0c;旨在为金融量化投资者、研究人员和学生提供稳定、准确、易用的A股历史数据和相关金融数据。其核心是一个 Python 库&#xf…

微信小程序-1-微信开发者工具环境搭建和初始化创建项目

文章目录1 小程序概述1.1 什么是微信小程序1.2 大前端概念1.3 账号注册1.4 开发流程1.5 小程序成员2 创建项目2.1 创建项目流程2.2 创建项目2.3 本地开发支持http3 项目目录3.1 项目目录结构3.2 配置文件3.2.1 app.json(全局配置)3.2.2 xxx.json(页面配置)3.2.3 project.config…

Go语言开发AI应用

为什么选择Go语言开发AI应用在人工智能快速发展的今天&#xff0c;选择合适的编程语言对于AI应用的成功至关重要。虽然Python长期以来被认为是AI开发的首选语言&#xff0c;但Go语言正在逐渐崭露头角&#xff0c;成为AI应用开发的有力竞争者。Go语言的核心优势1. 卓越的性能表现…

10. 游戏开发中的TCP与UDP

1.TCP和UDP 2.TCP为什么慢于UDP 3.可靠UDP1.TCP和UDP 1).通过打电话的方式说明TCP和UDPa.TCP(传输控制协议), 就像打电话- 需要先拨号, 接通, 问候(建立连接)- 你一句, 我一句, 对方没有听清会要求你重复(确认与重传)- 保证对话有条不紊, 内容准确无误(可靠, 有序)- 如果信号不…

CMap常用函数

CMap 是 MFC 中用于存储键值对&#xff08;key-value&#xff09;的关联容器类&#xff0c;类似于 C 标准库中的 std::map&#xff0c;但依赖 MFC 框架实现。它采用哈希表&#xff08;Hash Table&#xff09;作为底层数据结构&#xff0c;支持高效的键值查找、插入和删除操作。…

Rocky9.0去堆叠双发arp(支持“ARP 广播双发”)

摘要 在去堆叠/MLAG 场景下&#xff0c;默认 bonding 只会以单口回复 ARP&#xff0c;另一台交换机收不到 ARP Reply。本文在 Linux bonding 驱动中增加参数 arp_broadcast_mode&#xff0c;当开启时对 ARP 包临时切换到 广播模式&#xff0c;实现双口同时发 ARP Reply。文内提…

网页连接摄像头

摄像机处理 <!-- camera_solve.html --> <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>…

FPGA雷达信号处理之:自适应门限阈值

一、原理 参考这个博主&#xff0c;讲的很仔细&#xff1a;基于脉冲功率的雷达脉冲参数检测原理详解 二、FPGA实现 使用system generator搭建算法模型如下&#xff1a; 在这里&#xff0c;滤波器窗长度为8&#xff0c;原博主设置为50效果更好&#xff0c;门限公式如下&#xf…

Vue 中实现选中文本弹出弹窗的完整指南

在现代 Web 应用中&#xff0c;选中文本后显示相关操作或信息是一种常见的交互模式。本文将详细介绍如何在 Vue 中实现选中文本后弹出弹窗的功能&#xff0c;包括其工作原理、多种实现方式以及实际项目中的应用示例。 一、实现原理 1. 文本选中检测机制 浏览器提供了 Select…

第4节-排序和限制-FETCH

摘要: 在本教程中&#xff0c;你将学习如何使用 PostgreSQL 的 FETCH 子句从查询中检索部分行。 PostgreSQL FETCH 简介 在 PostgreSQL 中&#xff0c;OFFSET 子句的作用类似于 LIMIT 子句。FETCH 子句允许你限制查询返回的行数。 LIMIT 子句并非 SQL 标准的一部分。不过&#…