前端策略模式:react hooks 表单验证

news2025/1/16 17:52:48

react hooks 表单验证—策略模式

1.前置知识概述

  • 策略模式的定义

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,策略模式的目的就是将算法的使用与算法的实现分离开来,避免使用多重条件判断
策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,Context 接受客户的请求,随后把请求委托给某一个策略类。

2.背景

  • 在日常的需求开发中避免不了各种表单的校验,非空,长度,最大最小值等等,可能很多同学为了编写方便会通过多个if…else 来判断不同的校验逻辑。
  • 比如现在提一个简单的需求,需要用户输入姓名、年龄、日期,姓名不能为空并且长度不能超过10,年龄不能为空,日期可以为空但能超过当前时间。如果校验失败需要toast提示,(注:姓名如果校验失败需要将label标红)
  • 在上述这样简单的场景下,方便起见可能就会这样处理,但是一般的校验都会伴随着各种响应,当逻辑复杂时,各种条件分支,校验和业务逻辑混杂在一起,就会非常难以维护,并且不同页面可能会有相同的校验逻辑无法进行抽离复用造成很多重复代码
  const submit = () => {
    const {
      age,
      name,
      date
    } = formData;

    if (!name) {
      console.log('将label变为红色');
      return  toast('姓名不能为空');
    }

    if (name.length > 10) {
      console.log('将label变为红色');
      return  toast('姓名长度不能超过10');
    }
  
    if (!age) {
      return toast('年龄不能为空');
    }

    if (date) {
      const dateVal = new Date(date);
      const dateMax = new Date();

      if (dateVal > dateMax) {
        return toast('不能大于当前日期');
      }
    }
  }

3.校验器的功能

  • 开始编码之前需要清楚的是,我们预期它有什么功能,知道需要什么才能去设计去开发。很多同学写程序没有思路就在于没有想好自己想要的功能,或者使用方式。

  • 校验器的作用
    1.封装校验逻辑,相同场景可复用,并且职责单一,用来判断属性值是否合法。

  • 需要实现的功能
    1.支持对属性添加单个规则,或者多个对规则
    2.支持对检测到的非法值执行相应的回调

  • 使用方式:

// 对属性只加多个校验规则 对name的值进行校验 校验是否为空 长度是否超过10
validator.add(formDate.name, [
  {
    type: TYPES.isEmpty,
    rules: {
      errorMsg: '姓名不能为空',
      errCallBack: (errMsg) => {console.log(errMsg, '校验失败的回调 处理name的label变红色')}
    }
  },{
    type: TYPES.maxLength,
    rules: {
      errorMsg: '姓名长度不能超过10',
      maxlength: 10
    }
  }
]);

// 对属性值加单个校验规则 校验age是否为空
validator.add(formDate.age, [
  {
    type: TYPES.isEmpty,
    rules: {
      errorMsg: '年龄不能为空'
    }
  }
]);

// 开始校验
const errorMsg = validator.start();

// 判断是否通过校验
if (errorMsg) {
  console.log(errorMsg, '未通过校验')
}

4.实现一个校验器

4.1 封装多种校验策略

  • 可根据具体的业务需求封装同的校验规则,主要的校验思路就是通过传入被校验的值 和校验它所需要的参数进行判断,是否合法。这里需要什么参数完全由业务需求而定
  • 比如我们要判断长度,那就需要maxlength指定一个期望的长度,要判断日期是否大于某一天,那就需要maxDate指定不能超过的日期值。
  • 所以具体情况具体分析,当有新的校验规则时,只需要新增策略即可。
// 所有的校验规则
const strategies = {
  // 空值校验
  isEmpty: (value, rules) => {

    // rules使用对象传参 可以使得不同规则下定义不同的参数
    const {errorMsg, errCallBack} = rules;
    if (!value && value !== 0) {
      errCallBack && errCallBack(errorMsg);
      return errorMsg;
    }
  },

  // 长度校验
  maxLength: (value, rules) => {
    const {maxlength, errorMsg, errCallBack} = rules;

    if (value.length > maxlength) {
      errCallBack &&  errCallBack(errorMsg);
      return errorMsg;
    }
  },

  // 大于指定时间校验
  gtDate: (value, rules) => {
    const {maxDate, errorMsg, errCallBack} = rules;
    const dateVal = new Date(value);
    const dateMax = new Date(maxDate);

    if (dateVal > dateMax) {
      errCallBack &&  errCallBack(errorMsg);
      return errorMsg;
    }
  }
};

4.2 实现校验器类

  • 帮我们承接校验的逻辑,作为一个校验的入口
  • 主要就是实现一个add,和一个start方法,add用来找到对应的规则函数传入对应参数缓存到队列中等待被执行;start用来遍历当前的规则校验队列。
// 校验器
class Validator {
  constructor() {
    // 用来存储校验策略的队列
    this.cach = [];
  }

  // 支持添加多个和单个
  add (value, rulesArgs = []) {
    // 兼容单个规则的写法
    if (!(rulesArgs instanceof Array)) {
      rulesArgs = [rulesArgs];
    }

    rulesArgs.forEach(ruleArg => {
      const {type , rules} = ruleArg;
      this.cach.push(() => {
        // 返回对应规则策略的执行
        return strategies[type] && strategies[type](value, rules);
      });
    })
  }

  // 开始校验
  start () {
    // 根据add的顺序逐一校验
    for (const strategyFn of this.cach) {
      const msg = strategyFn();

      // 遇到非法结果 退出循环
      if (msg) {
        return msg;
      }
    }
  }
}

export const validator = () => {
  return new Validator();
}

5.结合react体验一下吧

  • 效果如下:
    请添加图片描述
  • 在submit提交的时候,我们只需要将校验的逻辑收敛到validateForm中,不需要其在提交时进行各种校验的分支逻辑,方便维护,条例清晰。
import styles from './index.module.less'; 
import { Input, Form, Button, Toast, DatePicker } from 'antd-mobile';
import { useState } from 'react';
import moment from 'moment';

import { validator as getValidator, TYPES } from '../tools/v';

// 方便多个类名的书写
const classNames = (classNames) => {
  let result = '';
  classNames.forEach(name => {
    if (styles[name]) {
      result += ` ${styles[name]}`;
    }
  });
  return result;
}

function Test() {
  // 表单数据
  const [formData, setformData] = useState({
    age: '',
    name: '',
    date: ''
  });

  // 针对某个数据的校验做特殊样式处理
  const [formErr, setFormErr] = useState({
    name: false
  });

  // 控制时间选择器的显示
  const [visible, setVisible] = useState(false);

  const toast = (msg) => {
    Toast.show({
      content: msg
    });
  }

  // 校验表单数据
  const validateForm = (formData) => {
    const validator = getValidator();
    const {age, name, date} = formData;

    // 添加校验规则
    validator.add(name, [
      {
        type: TYPES.isEmpty,
        rules: {
          errorMsg: '姓名不能为空',
          // 一些定制化的错误处理
          errCallBack: () => {
            setFormErr(state => ({...state, name: true}));
          }
        }
      }, {
        type: TYPES.maxLength,
        rules: {
          maxlength: 10,
          errorMsg: '姓名不得超过10个字',
          errCallBack: () => {
            setFormErr(state => ({...state, name: true}));
          }
        }
      }
   ]);

    // 校验年龄
    validator.add(age, {
      type: TYPES.isEmpty,
      rules: {
        errorMsg: '年龄不能为空'
      }
    });

    // 校验日期
    validator.add(date, {
      type: TYPES.gtDate,
      rules: {
        errorMsg: '日期不能大与当前时间',
        maxDate: moment(new Date()).format('YYYY-MM-DD')
      }
    });

    const errMsg = validator.start();
    // 统一处理toast
    if (errMsg) {
      toast(errMsg);
      return true;
    }
  }

  // 提交表单
  const submit = () => {
    const isIllegal = validateForm(formData);

    if (isIllegal) {
      return;
    }
    toast('提交成功');
  }

  return (
    <div className={styles.wrap}>
      <Form
        layout='horizontal'
        footer={
          <Button block type='submit' color='primary' size='large' onClick={submit}>
            提交
          </Button>
        }
        mode='card'
      >
        <Form.Item
          className={classNames(['name', formErr.name ? 'error' : '']) }
          label='姓名'
        >
          <Input
            maxLength='20'
            placeholder='请输入姓名'
            value={formData.name}
            onChange={val => {
              setFormErr(state => ({...state, name: false}))
              setformData(state => ({...state, name: val}))
            }}
          />
        </Form.Item>
        <Form.Item
          label='年龄'
        >
          <Input
            placeholder='请输入年龄'
            max={120}
            type='number'
            value={formData.age}
            onChange={val => {
              setformData(state => ({...state, age: val}))
            }}
          />
        </Form.Item>
        <Form.Item>
          <Button onClick={() => setVisible(true)}>{formData.date ? formData.date : '时间选择'}</Button>
        </Form.Item>
      </Form>
      <DatePicker
        title='时间选择'
        visible={visible}
        onClose={() => {
          setVisible(false);
        }}
        onConfirm={val => {
          setformData(state => ({...state, date: moment(val).format('YYYY-MM-DD')}))
        }
      }
      />
    </div>
  );
}

export default Test;

6.总结

  • 平时学习设计模式,可能感觉很难应用到业务开发中,又很少封装一些库或工具,后面我要会继续结合一些设计模式,应用到实际的业务开发场景,一起学习。
  • 以上的代码肯定还是不够优雅的,仅仅为了给同学们提供一个思路和方向,本人也是借鉴JavaScript设计模式与开发实践中的案例进行一些调整和完善。
  • 如有错误的地方请大家及时指出,避免误导其他同学~

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

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

相关文章

Day14--商品详情-渲染商品导航区域

1.渲染商品导航区域 我的操作&#xff1a; 1》在UI结构处加上一个展示的位置 2》在data中写配置项 官方文档&#xff1a; 我的书写&#xff1a; 3》补齐其UI结构在1》中的留存的位置上 4》看看效果图&#xff1a; 5》美化其样式【固定定位】 ******************************…

WinCC通过OPCUA链接Kepware(WinCC作为客户端)

OpcUaServerWinCC服务暂停导致WinCC OPCUA功能不能使用 解决方法: 第一步 下载 连通包 connectivity pack 第二步 使用 Sim_EKB_Install_2017_06_03.exe 授权 connectivity 对应版本号 第三步 运行起来WinCC,查看OpcUaServerWinCC服务是否启动,否则重启。 网上关于OPCUA配置…

七客咖啡50店齐开,拓展咖啡赛道

疫情似乎成了餐饮界品牌的篦子&#xff0c;它检验着品牌的强弱及面对突发状况时的应对能力&#xff0c;从2020年至今&#xff0c;不少品牌都关门以求自保或直接破产。然而&#xff0c;七客咖啡却在6月3号微博发文宣布再开50家门店&#xff0c;具体在上海、武汉、成都、广州等地…

Spark系列之Spark应用程序运行机制

title: Spark系列 第六章 Spark应用程序运行机制 6.1 Spark的基本运行流程 Spark任务的核心执行流程主要分为四大步骤&#xff1a; Driver工作&#xff1a;Build DAG DAGScheduler工作&#xff1a;Split DAG to Stage TaskScheduler工作&#xff1a;Change Stage to TaskSet…

java成神之路-基础篇

java成神之路-基础篇 最近关注了个 主播&#xff0c;Hollis 阿里巴巴的一位专家&#xff0c;该博主总结了一份java成神之路的知识图谱&#xff0c;基于图谱打算出 几期总结文档也有助于自己巩固与学习。于是有了这篇文章。 图谱有着么几个阶段&#xff0c;附件我放到资源里 基…

最全面的SpringMVC教程(三)——跨域问题

前言 本文为 【SpringMVC教程】跨域问题 相关内容介绍。当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同时&#xff0c;就会产生跨域。那么究竟什么是跨域&#xff0c;跨域问题该如何解决&#xff0c;本文具体将对同源策略&#xff0c;什么是跨域&#xff0…

Kvaser Leaf light HS v2 | 如何使用Excel发送和接收CAN报文数据

从1980年代&#xff0c;Kvaser就开始CAN产品的研发&#xff0c;在相关产品开发领域有近40多年的经验&#xff0c;对CAN和相关总线技术有着非常深入的研究。我们将分享一些有趣的发现和一些特定情况的技术处理&#xff0c;欢迎关注❤️广州智维电子科技有限公司❤️&#xff01;…

【微信小程序】冒泡事件与非冒泡事件、将文章数据从业务中分离、wxml的模块化

&#x1f3c6;今日学习目标&#xff1a;第十四期——冒泡事件与非冒泡事件、将文章数据从业务中分离、wxml的模块化 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;30分钟 &#x1f389;专栏系列&#xff1a;我的第…

Day14--商品详情-渲染商品详情的数据并优化详情页的显示

提纲挈领&#xff1a; 那么如何在小程序中将这些html的字符串渲染成这莫好看的结构呢&#xff1f; 官方文档&#xff1a;【使用uni-ui组件库中的rich-text组件】 1.渲染商品详情信息 我的操作&#xff1a; 1》在页面结构中&#xff0c;使用 rich-text 组件&#xff0c;将带有…

盘点一个批量提取pdf文件目标信息的实用案例

点击上方“Python爬虫与数据挖掘”&#xff0c;进行关注回复“书籍”即可获赠Python从入门到进阶共10本电子书今日鸡汤你若盛开,清风自来。大家好&#xff0c;我是皮皮。一、前言前几天在帮助粉丝解决问题的时候&#xff0c;遇到一个简单的小需求&#xff0c;需要批量提取pdf文…

【零基础入门SpringMVC】第六期——尾声

一、注解配置SpringMVC 采用全注解开发&#xff0c;替代我们的web.xml和SpringMVC的核心配置文件 我们需要创建对应的配置类&#xff0c;继承AbstractAnnotationConfigDispatcherServletInitializer 使用的Servlet版本要求在3.0以上项目启动后容器会找到配置了&#xff0c;基于…

台积电跪舔美国,日本却醒悟了而选择独立发展芯片产业

近期台积电大举包机10架将精英人才和设备转往美国引发争议&#xff0c;然而这个时候日本却选择了独立发展芯片产业的道路&#xff0c;摆脱美国的限制&#xff0c;显然日本清醒地认识到依赖美国不会有好结果。台积电之前还在左右摇摆&#xff0c;希望既能继续获得美国芯片的订单…

测试用例的重要性,看完这篇就够了

测试用例对于测试工作的作用&#xff1a;1、指导测试的实施测试用例主要适用于集成测试、系统测试和回归测试。在实施测试时测试用例作为测试的标准&#xff0c;测试人员一定要按照测试用例严格按用例项目和测试步骤逐一实施测试。并对测试情况记录在测试用例管理软件中&#x…

干货 | 数字经济创新创业——如何发展绿色经济

下文整理自清华大学大数据能力提升项目能力提升模块课程“Innovation & Entrepreneurship for Digital Economy”&#xff08;数字经济创新创业课程)的精彩内容。主讲嘉宾&#xff1a;Kris Singh: CEO at SRII, Palo Alto, CaliforniaVisiting Professor of Tsinghua Unive…

[附源码]计算机毕业设计springboot房屋租赁系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【毕业设计】27-基于单片机的家庭监控及防盗报警_热释电报警_人体系统工程设计(原理图+源代码+仿真+实物照片+答辩论文)

【毕业设计】27-基于单片机的家庭监控及防盗报警/热释电报警/人体系统工程设计&#xff08;原理图源代码仿真实物照片论文&#xff09; 文章目录【毕业设计】27-基于单片机的家庭监控及防盗报警/热释电报警/人体系统工程设计&#xff08;原理图源代码仿真实物照片论文&#xff…

【Java实战】工作中规范使用Java集合

目录 一、前言 二、规范使用Java集合 1.【强制】关于 hashCode 和 equals 的处理&#xff0c;遵循如下规则&#xff1a; 2.【强制】判断所有集合内部的元素是否为空&#xff0c;使用 isEmpty() 方法&#xff0c;而不是 size() 0 的方式。 3.【强制】在使用 java.util.str…

接口自动化测试实践指导(中):接口测试场景有哪些

在第一篇文章中详细给小伙伴们讲解了接口自动化需要做哪些准备工作&#xff0c;准备工作中最后一步接口测试用例设计是非常重要的一个环节&#xff0c;用例设计的好不好&#xff0c;直接关系到我们的测试质量。那如何进行测试用例设计呢&#xff1f;这里呢我结合自身经验&#…

PYTHON 用几何布朗运动模型和蒙特卡罗MONTE CARLO随机过程模拟股票价格可视化分析耐克NKE股价时间序列数据...

原文链接&#xff1a;http://tecdat.cn/?p27099 金融资产/证券已使用多种技术进行建模。该项目的主要目标是使用几何布朗运动模型和蒙特卡罗模拟来模拟股票价格。该模型基于受乘性噪声影响的随机&#xff08;与确定性相反&#xff09;变量&#xff08;点击文末“阅读原文”获取…

【 医学影像| 数据预处理】

影像读取及预处理&#xff1a;预处理后的数据集建议保存在本地&#xff0c;可以减少训练时的部分资源消耗。里面提到了归一化的 对分割的一些理解&#xff1a;基于深度学习来做医学图像处理&#xff0c;主要的工作集中在了数据预处理部分&#xff1a;深入理解医学图像的格式和特…