React IpAddressInput组件问题:删除数字意外影响其他网段
解决react-text-mask实现IPv4输入框的删除异常问题
你的问题核心来自三个关键缺陷:
- 实时验证逻辑过于严苛:只有完全符合IPv4标准的输入才允许更新状态,导致删除过程中的中间状态(如
123.4_6.789.222这类不完整网段)无法被组件捕获,mask只能自行处理输入引发跨段移位。 keepCharPositions设置错误:关闭该属性后,删除字符时mask会重新排列整个输入内容,直接导致相邻网段的字符被错误移动。- 静态掩码无法适配IPv4动态规则:固定的掩码模板没有考虑网段0-255的范围限制,也未处理删除时的网段边界保护。
修复步骤
- 放宽实时验证,仅做基础分段检查
实时输入时只验证内容为数字和点号、点号数量不超过3,把严格的IPv4验证放到失焦或提交环节。 - 开启
keepCharPositions
保留字符位置,让删除操作仅影响当前网段,不会波及其他网段。 - 使用动态掩码函数
根据当前输入的网段内容自动调整掩码规则,处理0-255的范围限制,同时支持输入满3位后自动跳段、删除时自动回退。 - 添加失焦格式化逻辑
输入框失焦时,自动将不完整的IP修正为合法的IPv4格式。
修改后的完整代码
import React, { useState, useEffect, useCallback } from 'react'; import MaskedInput from 'react-text-mask'; const IpAddressInput = ({ initialValue = '', onChange, onBlur }) => { const [value, setValue] = useState(initialValue); // 动态生成适配IPv4的掩码 const createIPMask = useCallback((rawValue) => { const mask = []; const segments = rawValue.split('.').filter(Boolean); segments.forEach((segment, index) => { const num = parseInt(segment, 10); if (!isNaN(num)) { // 根据当前网段数值调整掩码规则,确保不超过255 if (num > 255) { mask.push(/[0-2]/, /[0-5]/, /[0-5]/); } else if (num >= 100) { mask.push(/\d/, /\d/, /\d/); } else if (num >= 10) { mask.push(/\d/, /\d/, /\d?/); } else { mask.push(/\d/, /\d?/, /\d?/); } } else { mask.push(/\d/, /\d?/, /\d?/); } // 非最后一个网段添加点号分隔符 if (index < 3) { mask.push('.'); } }); // 补全剩余网段和点号,确保掩码长度符合IPv4结构 while (mask.length < 15) { const segmentIndex = Math.floor(mask.length / 4); if (segmentIndex < 4) { mask.length % 4 === 3 ? mask.push('.') : mask.push(/\d/, /\d?/, /\d?/); } } return mask.slice(0, 15); }, []); // 实时输入处理:允许中间状态,仅做基础合法性检查 const handleChange = useCallback((event) => { const newValue = event.target.value; const isValid = /^(\d{1,3}\.?){0,4}$/.test(newValue.replace(/\.$/, '')); if (isValid) { setValue(newValue); onChange(newValue); } }, [onChange]); // 失焦时格式化IP为合法IPv4格式 const handleBlur = useCallback((event) => { let formattedValue = event.target.value.trim(); const segments = formattedValue.split('.').map(seg => { // 去除前导零(单独的0保留) const trimmed = seg.replace(/^0+(?=\d)/, ''); // 空网段默认设为0 if (!trimmed) return '0'; // 限制数值在0-255之间 const num = parseInt(trimmed, 10); return Math.max(0, Math.min(255, num)).toString(); }); // 补全或截断到4个网段 const finalSegments = [...segments.slice(0, 4)]; while (finalSegments.length < 4) { finalSegments.push('0'); } const finalIP = finalSegments.join('.'); setValue(finalIP); onChange(finalIP); onBlur && onBlur(finalIP); }, [onChange, onBlur]); useEffect(() => { setValue(initialValue); }, [initialValue]); return ( <MaskedInput mask={createIPMask} value={value} onChange={handleChange} onBlur={handleBlur} placeholder="Enter IPv4 address" keepCharPositions={true} // 开启字符位置保留,防止删除移位 guide={true} // 显示引导字符,提升输入体验 /> ); }; export default IpAddressInput; // 父组件调用示例 function Settings() { const [ipAddress, setIpAddress] = useState(''); const handleIpAddressChange = (value) => { console.log('IP Address changed:', value); setIpAddress(value); }; const handleIpBlur = (value) => { console.log('IP Address formatted on blur:', value); }; return ( <div className="ControlContainer"> <h3 style={{ marginBottom: '13px' }}>IP Address</h3> <IpAddressInput initialValue={ipAddress} onChange={handleIpAddressChange} onBlur={handleIpBlur} /> </div> ); } export default Settings;
关键修改说明
- 动态掩码:根据当前输入的网段数值自动调整规则,既保证输入不超过255,又保留输入灵活性。
- 宽松实时验证:允许用户输入中间状态,避免mask因组件状态不更新而出现异常移位。
- 字符位置保留:彻底解决删除操作跨段影响其他网段的问题。
- 失焦格式化:自动修正不完整IP,确保最终输出符合IPv4标准。
内容的提问来源于stack exchange,提问作者kannan srank
相关产品推荐
相关产品推荐

