基于React的IP输入框实现
文章目录
- 前言
- 1. 目标效果
- 1. 代码
- 2. 部分内容解析
- 2.1 更新值
- 2.2 拖延症犯了,先到这里, 有空再补
- 3. 已知问题
前言
本文代码主要基于 React-Bootstrap 实现。
在做IP输入时,本想寻找已实现的方案,但苦于没有寻找到合适,大多采用同一种方式:用四个输入组合起来。
但我并不想这么 做。
备注:在一台新设备上调用 React-bootstrap 库时,我遇到 bootstrap 样式未生效的问题,原因是不能正确解析sass文件,执行 npm install node-sass
解决
1. 目标效果
- 在没有输入时,输入框中信息为空
- 在鼠标覆盖时,输入框中自动显示IP格式
_._._._
- 在鼠标离开时,如果输入框没有任何有效信息,输入框信息再次置为空
- 当聚焦时,输入框保持显示
_._._._
,并等待用户输入 - 用户输入时,覆盖掉对应的
_
- 输入
.
,则自动跳转到下一个字段 - 输入满
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. 已知问题
- 对粘贴IP部分片段不友好
- 如果输入位置不再该字段末位,满三个数字后,不会自动跳到下一个字段,也不会顺延
- bug 无穷尽…
基于React的IP输入框实现相关推荐
- IP输入框-基于Jquery
首先,在页面上显示IP输入框 <input type="text" name="ip_b_1" id="ip_b_1" class=& ...
- 基于React搭建一个美团WebApp
一:React基础准备 1.1React是一个专注于View层的组件库,React提供设计的时候目标是尽可能的是接近原生,没有设计出很多高级的功能,这也为后来开发者自定义功能模块提供了很大的空间 1. ...
- 基于React和Node.JS的表单录入系统的设计与实现
一.写在前面 这是一个真实的项目,项目已经过去好久了,虽然很简单,但还是有很多思考点,跟随着笔者的脚步,一起来看看吧.本文纯属虚构,涉及到的相关信息均已做虚构处理, 二.背景 人活着一定要有信仰,没有 ...
- 基于 react, redux 最佳实践构建的 2048
前段时间 React license 的问题闹的沸沸扬扬,搞得 React 社区人心惶惶,好在最终 React 团队听取了社区意见把 license 换成了 MIT.不管 React license ...
- 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(五)客户端介绍
开源倾情奉献系列链接 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(一)开放源代码 开源倾情奉献:基于.NET打造IP智能网络视频监控系统(二)基础类库介绍 开源倾情奉献:基于.NET打造I ...
- 基于React脚手架集成Cesium
基于React脚手架集成Cesium 文章目录 基于React脚手架集成Cesium 1. 安装环境 2. 创建项目 3. 引入Cesium 4. 修改文件 5. 参考链接 1. 安装环境 安装Nod ...
- value数字 vue_基于Vue开发数字输入框组件
随着 vue 越来越火热, 相关组件库也非常多啦, 只用轮子怎么够, 还是要造起来!!! 1.概述 vue组件开发的api:props.events和slots 2.组件代码 效果: (1)index ...
- 前段react技术架构图_基于 React 的可视化编辑平台实践
前言 前段时间发在朋友圈的一句话:各种自主搭建的平台,想起好多年各种DIY博客,行业门户网站,本质不变,变的是实现的手段了. 正文从这开始-- 本文主要介绍了基于 React 构建可视化编辑平台的实践 ...
- Mdebug:基于React开发的移动web调试工具
作者:thinkchen,腾讯 PCG 高级前端开发工程师 mdebug是腾讯新闻 TNTWEB 团队推出的基于React开发的新的web调试工具, 沉淀自腾讯新闻微信手 q 双插件多年的移动 web ...
最新文章
- vim+vundle配置
- 一个UI布局框架,以最少的代码实现UI设置及布局控制
- ADMT3.2域迁移之Server2003至Server2012系列(七)安装ADMT3.2
- Dalvik解释器源码到VMP分析
- delphi 常用属性/方法《转》
- MyBatis之八:需要说明的几个java api的生命周期以及封装
- 【C++ Priemr | 15】虚函数表剖析(二)
- 猎豹移动回应被谷歌下架:积极整改情况下被单方面下架
- linux文件系统选哪种,linux下几种文件系统的测试比较
- 网站前端和后台性能优化22
- Linux系统性能相关知识学习
- CSS可见格式化模型
- android学习笔记----多线程断点续传下载原理设计
- 震惊~~飞流android版使用体验!!
- 深入浅出TensorFlow2函数——tf.data.Dataset.shuffle
- 【流畅的Python学习笔记】2023.4.21
- 从头搭建rpc框架_#LearnByDIY-如何从头开始创建JavaScript单元测试框架
- 券商融资融券业务今年将适时试点
- ubuntu server 7.04(10.04版也行) 挂载移动硬盘
- 应用在电磁炉触控面板中的电容式触摸芯片
热门文章
- 关于python 如何实现数据内两两特征相乘
- 【FDM】开源免费的下载软件
- VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION(VGG网络)-论文阅读笔记
- 计算机系统的优化教案,2.2 优化计算机 教案
- 【Python第13课】字符串格式化
- uoj#399. 【CTSC2018】假面(概率期望)
- 弘辽科技:淘宝降权恢复后流量能恢复吗?怎么申诉?
- html视频播放器加片头广告,如何屏蔽视频网站的片头广告?一键屏蔽各大视频网站片头广告插件推荐...
- OfficeTeam- Excel常用技巧25条
- iptables+ipset自动封闭和解封频繁访问web服务的恶意IP