文章目录

  • 前言
  • 1. 目标效果
  • 1. 代码
  • 2. 部分内容解析
    • 2.1 更新值
    • 2.2 拖延症犯了,先到这里, 有空再补
  • 3. 已知问题

前言

本文代码主要基于 React-Bootstrap 实现。
在做IP输入时,本想寻找已实现的方案,但苦于没有寻找到合适,大多采用同一种方式:用四个输入组合起来。

但我并不想这么 做。

备注:在一台新设备上调用 React-bootstrap 库时,我遇到 bootstrap 样式未生效的问题,原因是不能正确解析sass文件,执行 npm install node-sass 解决

1. 目标效果

  1. 在没有输入时,输入框中信息为空
  2. 在鼠标覆盖时,输入框中自动显示IP格式 _._._._
  3. 在鼠标离开时,如果输入框没有任何有效信息,输入框信息再次置为空
  4. 当聚焦时,输入框保持显示 _._._._ ,并等待用户输入
  5. 用户输入时,覆盖掉对应的 _
  6. 输入 . ,则自动跳转到下一个字段
  7. 输入满 3 个数字,自动跳转到下一个字段



1. 代码

先上代码和注释


import { useCallback, useMemo, useState } from "react";
// import isAscii from "validator/lib/isAscii";
import Form from "react-bootstrap/Form";
import "bootstrap"function IPInput(props) {// 设置 IP 的基本格式const base = useMemo(() => "_._._._", []);// const [IP, setIP] = useState(() => {//     if (//         props.value === undefined ||//         props.value === "" ||//         props.value === null//     ) {//         return base;//     } else {//         return props.value;//     }// });// 设置当前光标的位置const setSelection = useCallback((element, start, end) => {element.selectionStart = start;if (end === undefined ||end === null) element.selectionEnd = start;else element.selectionEnd = end;}, []);// 原来是用了来避免重复属性的,但其实完全没有必要这么做// const usedProps = [//     "type",//     "onChange",//     "forceRender",//     "onMouseOver",//     "onMouseLeave",//     "onKeyDown",//     "onFocus",//     "onBlur",//     "value"// ]// const sendProps = Object.keys(props).filter((val) => {//     return !usedProps.includes(val);// }).reduce((preVal, curVal) => {//     preVal[curVal] = props[curVal];//     return preVal;// }, []);// const sendProps = props.return (<> <Form><Form.Group className="mb-3" controlId="IP Address"><Form.Label>IP Address</Form.Label><Form.Control {...props}type="text" value={props.value}// onPaste={(e) => {//     alert(JSON.stringify(e.clipboardData, null, 4));//     alert(e.target.value);//     console.log("on Paste", e);// }}onChange={(e) => {// self 保存当前输入框本身信息, 使用const防止修改// val 保存改变后的输入框中的值,可以通过它直接修改输入框的信息// location 保存输入框当前的光标信息const self = e.target;let val = self.value;let location = self.selectionStart;if (val.match(/^(\d){1}$/)) {// 如果 全选然后用一个数字覆盖输入时// 显示 ${NUM}._._._val = `${val}._._._`;} else if (val.match(/^(.){1}$/)) {// 如果全选,用任意一个非空白字符覆盖时// 显示base状态val = base;} else if (// 如果拷贝过来一个合法IP时val.match(/^(((\d){0,3})\.((\d){0,3})\.((\d){0,3})\.((\d){0,3}))(?=(_._._._))/) ||val.match(/(?<=(_._._._))(((\d){0,3})\.((\d){0,3})\.((\d){0,3})\.((\d){0,3}))/)) {// 显示该IPval = val.replace(base, "");} else if (// 如果 输入后,不符合IP规则(允许存在_),!val.match(/^((_)|((_)?(\d){0,3}(_)?))\.((_)|((_)?(\d){0,3}(_)?))\.((_)|((_)?(\d){0,3}(_)?))\.((_)|((_)?(\d){0,3}(_)?))$/)) {if (val.match(/[a-zA-Z~!@#$%^&*()+=`{}[\]\\|;':",/<>?。,、;:“”;‘’《》?!¥…() -]/)) {// 如果输入了以上字符(不属于IP规则内的),对输入光标的位置做调整;其他复杂输入不做判断// 因为不会应用这些字符, 所以对光标位置做退格处理// 当 location 是 0 时, 不做退格处理if (location !== 0)location --;}if (val.match(/[0-9]{4}/)) {// 正常输入情况下,匹配到连续四个数字大致有三种情况// 1. 将 . 去掉,但程序会把点再补回来,直接让光标自动退一个即可// 2. 用户主动输入了 4个 数字, 会将最后一个输入的数字去掉,光标保持原位// 3. 用户粘贴了 4个 数字, 因为此时粘贴过来的是一个非法值, 所以直接 过掉if (val.match(/^((_)|(_)?(\d){0,4}(_)?)\.((_)|(_)?(\d){0,4}(_)?)\.((_)|(_)?(\d){0,4}(_)?)\.((_)|(_)?(\d){0,4}(_)?)$/))// 上述第2种情况, 主动输入的第四个数字不会被应用// 所以做退格处理location --;}if (self.value === "") {// 当前值为空时,修改为 base 状态self.value = base;// setIP(self.value);if (props.onChange !== undefined){props.onChange(self.value, e);}setSelection(self, location);return ;}// 舍弃用户当前修改, 调整光标位置// 这里注意, 一定要先修改输入框的value, 再更新stateself.value = props.value;if (props.onChange !== undefined){props.onChange(self.value, e);}setSelection(self, location);return ;}if (val.match(/__/)) {// 不允许连续下划线出现location --;self.value = props.value;if (props.onChange !== undefined){props.onChange(self.value, e);}setSelection(self, location);return ;}// 当输入 合法时,做一系列检查和调整// ipArr 数组 存放IP的四个字段// IPSub 记录当前正在验证字段的位置let ipArr = val.split(".");let IPSub = 0;for (let i = 0; i < 4; i++) {// 字段位置可以理解为, 该字段最后一个数字的位置IPSub += ipArr[i].length;// 如果该字段中有 "_", 则要去掉 "_"if (ipArr[i].match(/^((_){1}(\d){1,3})$/)){// 当 "_" 在该字段的首位, 两种情况需要移动光标if (IPSub === location) {// 光标在该字段末位location --;} else if (IPSub - ipArr[i].length === location - (self.value.length - props.value.length)) {// 光标在该字段 数字的中间位置时// 可以通过 粘贴 _1 来测试location --;}// 将 "_" 去掉ipArr[i] = ipArr[i].replaceAll("_", "");// 去掉 "_" 后, 该字段长度减一IPSub --;}if (ipArr[i].match(/^((\d){1,3}(_){1})$/)){// 如果下划线在数字后面// 如果 光标在该字段末位, 光标需要退格if (location === IPSub) location--;ipArr[i] = ipArr[i].replaceAll("_", "");IPSub --;}if (ipArr[i].match(/^((_){1}(\d){1,3}(_){1})$/)){// 如果前后都有下划线// 如果 光标在该字段末位, 光标需要退格2格if (location === IPSub) location -= 2;// 如果光标在 数字中间, 光标退格一位else if (location > IPSub - ipArr[i].length && location < IPSub ) location --;ipArr[i] = ipArr[i].replaceAll("_", "");IPSub --;IPSub --;}// 字段为空 补齐 _if (ipArr[i] === "") ipArr[i] = "_";// 如果字段以 0 开头,则去零if (ipArr[i].match(/^0[0-9]$/)) {ipArr[i] = String(Number(ipArr[i]));if (location !== 0)if (location !== IPSub - ipArr[i].length -1 )location--;}if (ipArr[i].match(/^0[1-9]{2}$/)) {ipArr[i] = String(Number(ipArr[i]));if (location !== 0)if (location !== IPSub - ipArr[i].length -1 )location--;}if (ipArr[i].match(/^(00)[0-9]$/)) {ipArr[i] = String(Number(ipArr[i]));location -= 2;}// _ 和 0 清理掉后if (location === IPSub) {// 如果已经输入了三个数字,自动跳到下一个字段if (ipArr[i].length === 3)location ++;}// 别忘了给 IPSub 补上 . 的位置IPSub ++;}// 为 输入框赋新值self.value = ipArr.join(".");setSelection(self, location);// setIP(self.value);if (props.onChange !== undefined){props.onChange(self.value, e);}// setIP(self.value);}}forceRender={props.forceRender}onMouseOver={(e) => {// 在鼠标覆盖时, 如果原有值为空, 显示base 状态const self = e.target;if (self.value === "" ||self.value === undefined ||self.value === null) {self.value = base;}if (props.onMouseOver !== undefined) {props.onMouseOver(e);}}}onMouseLeave={(e) => {// 当鼠标离开时, 如果为 base 状态, 清除内容const self = e.target;if ( self === document.activeElement ) return ;if (self.value === base) {self.value = "";}if (props.value === base) {// setIP("");if (props.onChange !== undefined) {props.onChange(self.value, e);}}if (props.onMouseLeave !== undefined) {props.onMouseLeave(e);}}}onMouseUp={(e) => {// 当鼠标点击抬起时// 如果当前为 内容为空, 自动补充 base 状态// 如果当前是 base 状态, 那么光标移动到 0 位置const self = e.target;if (self.value === "" ||self.value === undefined ||self.value === null) {self.value = base;if (props.onChange !== undefined) {props.onChange(self.value, e);}setSelection(self, 0);}if (self.value === base) {self.value = base;if (props.onChange !== undefined) {props.onChange(self.value, e);}setSelection(self, 0);}if (props.onMouseUp !== undefined) {props.onMouseUp(e);}}}onFocus={(e) => {// 输入框聚焦时, 如果值为空, 显示base 状态// 如果 值为 base 状态, 光标自动置为首位const self = e.target;if (self.value === "" ||self.value === undefined ||self.value === null) {self.value = base;if (props.onChange !== undefined) {props.onChange(self.value, e);}setSelection(self, 0);}if (self.value === base) {self.value = base;if (props.onChange !== undefined) {props.onChange(self.value, e);}self.selectionStart = 0;self.selectionEnd = 0;}if (props.onFocus !== undefined) {props.onFocus(e);}}}onBlur={(e) => {// 当前输入框失去焦点时,若输入框内容为 base 状态,就修改成 空状态const self = e.target;if (self.value === base) {self.value = "";// setIP(self.value);if (props.onChange !== undefined) {props.onChange(self.value, e);}} if (props.onBlur !== undefined) {props.onBlur(e);}}}// inBoxClass={"forbid-typewriting"}onKeyDown={(e) => {const self = e.target;const curLocation = self.selectionStart;if (e.key === "Backspace") {// 当按下退格键时, 如果是base 状态, 光标置为首位if (self.value === base) {self.selectionStart = 0;self.selectionEnd = 0;}}if (e.key === "v") {// 当按下 v 键时, 如果是base 状态, 光标置为首位// 主要目的是为了 CV 操作if (self.value === base) {self.selectionStart = 0;self.selectionEnd = 0;}}if (e.key === ".") {// 当按下点时, 自动跳转到下一个字段let relocation = self.value.indexOf(".", curLocation);self.selectionStart = relocation;self.selectionEnd = relocation;}if (props.onKeyDown !== undefined) {props.onKeyDown(e);}}}/></Form.Group></Form></>)}export default IPInput;

调用组件

import IPInput from "./Input/IPInput";
import { useCallback, useState } from "react";
import { Container, Row, Col } from "react-bootstrap";function App(props) {// 可以为 IP 赋初值, 但必须是一个合法IPconst [IP, setIP] = useState();// 封装修改IP的函数const handleIPChange = useCallback((val) => {setIP(val);}, []);return (<ContainerclassName="mt-5"><RowclassName="mb-3"><Col><IPInput value={IP}onChange={handleIPChange}/></Col></Row></Container>);
}export default App;

2. 部分内容解析

2.1 更新值

self.value = props.value;
if (props.onChange !== undefined){props.onChange(self.value, e);
}
setSelection(self, location);

这部分为什么这么写?
因为如果是直接通过onChange函数修改 State,而State的值是我们经过一系列计算得到的,跟原来的不同,Input 会认为,它的值改变了,会重置光标。
所以,我们要先更新 Input 的值,再更新State。

2.2 拖延症犯了,先到这里, 有空再补

3. 已知问题

  1. 对粘贴IP部分片段不友好
  2. 如果输入位置不再该字段末位,满三个数字后,不会自动跳到下一个字段,也不会顺延
  3. bug 无穷尽…

基于React的IP输入框实现相关推荐

  1. IP输入框-基于Jquery

    首先,在页面上显示IP输入框 <input type="text" name="ip_b_1" id="ip_b_1" class=& ...

  2. 基于React搭建一个美团WebApp

    一:React基础准备 1.1React是一个专注于View层的组件库,React提供设计的时候目标是尽可能的是接近原生,没有设计出很多高级的功能,这也为后来开发者自定义功能模块提供了很大的空间 1. ...

  3. 基于React和Node.JS的表单录入系统的设计与实现

    一.写在前面 这是一个真实的项目,项目已经过去好久了,虽然很简单,但还是有很多思考点,跟随着笔者的脚步,一起来看看吧.本文纯属虚构,涉及到的相关信息均已做虚构处理, 二.背景 人活着一定要有信仰,没有 ...

  4. 基于 react, redux 最佳实践构建的 2048

    前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...

  5. 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(五)客户端介绍

    开源倾情奉献系列链接 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(二)基础类库介绍 开源倾情奉献:基于.NET打造I ...

  6. 基于React脚手架集成Cesium

    基于React脚手架集成Cesium 文章目录 基于React脚手架集成Cesium 1. 安装环境 2. 创建项目 3. 引入Cesium 4. 修改文件 5. 参考链接 1. 安装环境 安装Nod ...

  7. value数字 vue_基于Vue开发数字输入框组件

    随着 vue 越来越火热, 相关组件库也非常多啦, 只用轮子怎么够, 还是要造起来!!! 1.概述 vue组件开发的api:props.events和slots 2.组件代码 效果: (1)index ...

  8. 前段react技术架构图_基于 React 的可视化编辑平台实践

    前言 前段时间发在朋友圈的一句话:各种自主搭建的平台,想起好多年各种DIY博客,行业门户网站,本质不变,变的是实现的手段了. 正文从这开始-- 本文主要介绍了基于 React 构建可视化编辑平台的实践 ...

  9. Mdebug:基于React开发的移动web调试工具

    作者:thinkchen,腾讯 PCG 高级前端开发工程师 mdebug是腾讯新闻 TNTWEB 团队推出的基于React开发的新的web调试工具, 沉淀自腾讯新闻微信手 q 双插件多年的移动 web ...

最新文章

  1. vim+vundle配置
  2. 一个UI布局框架,以最少的代码实现UI设置及布局控制
  3. ADMT3.2域迁移之Server2003至Server2012系列(七)安装ADMT3.2
  4. Dalvik解释器源码到VMP分析
  5. delphi 常用属性/方法《转》
  6. MyBatis之八:需要说明的几个java api的生命周期以及封装
  7. 【C++ Priemr | 15】虚函数表剖析(二)
  8. 猎豹移动回应被谷歌下架:积极整改情况下被单方面下架
  9. linux文件系统选哪种,linux下几种文件系统的测试比较
  10. 网站前端和后台性能优化22
  11. Linux系统性能相关知识学习
  12. CSS可见格式化模型
  13. android学习笔记----多线程断点续传下载原理设计
  14. 震惊~~飞流android版使用体验!!
  15. 深入浅出TensorFlow2函数——tf.data.Dataset.shuffle
  16. 【流畅的Python学习笔记】2023.4.21
  17. 从头搭建rpc框架_#LearnByDIY-如何从头开始创建JavaScript单元测试框架
  18. 券商融资融券业务今年将适时试点
  19. ubuntu server 7.04(10.04版也行) 挂载移动硬盘
  20. 应用在电磁炉触控面板中的电容式触摸芯片

热门文章

  1. 关于python 如何实现数据内两两特征相乘
  2. 【FDM】开源免费的下载软件
  3. VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION(VGG网络)-论文阅读笔记
  4. 计算机系统的优化教案,2.2 优化计算机 教案
  5. 【Python第13课】字符串格式化
  6. uoj#399. 【CTSC2018】假面(概率期望)
  7. 弘辽科技:淘宝降权恢复后流量能恢复吗?怎么申诉?
  8. html视频播放器加片头广告,如何屏蔽视频网站的片头广告?一键屏蔽各大视频网站片头广告插件推荐...
  9. OfficeTeam- Excel常用技巧25条
  10. iptables+ipset自动封闭和解封频繁访问web服务的恶意IP