React千分位输入框:退格/删除键失效问题及简洁实现问询
问题解决:千分位输入框的删除功能修复与简化实现
一、修复现有代码的删除问题
原代码中,按下Backspace/Delete碰到分隔符时仅移动光标,未执行实际删除操作。修改onKeyDown函数,在检测到分隔符时直接删除对应字符并更新状态:
import React, { useState, useRef } from 'react'; const NumericInput = () => { const [value, setValue] = useState(''); const inputRef = useRef(null); const onKeyDown = (e) => { const inputElement = inputRef.current; const caretStart = inputElement.selectionStart; const caretEnd = inputElement.selectionEnd; // 处理Backspace删除分隔符 if (e.key === 'Backspace' && caretStart === caretEnd && caretStart > 0) { const previousChar = inputElement.value.charAt(caretStart - 1); if (previousChar === ',') { e.preventDefault(); // 计算原始值中对应的位置(减去前面分隔符的数量) const separatorCountBefore = inputElement.value.slice(0, caretStart - 1).match(/,/g)?.length || 0; const originalPosition = caretStart - 1 - separatorCountBefore; // 更新原始值,删除对应位置的字符 const newOriginalValue = value.slice(0, originalPosition) + value.slice(originalPosition + 1); setValue(newOriginalValue); // 延迟设置光标,确保状态更新后输入框已重新渲染 setTimeout(() => { inputElement.setSelectionRange(caretStart - 1, caretStart - 1); }, 0); return; } } // 处理Delete删除分隔符 if (e.key === 'Delete' && caretStart === caretEnd && caretEnd < inputElement.value.length) { const nextChar = inputElement.value.charAt(caretStart); if (nextChar === ',') { e.preventDefault(); // 提取去掉分隔符后的原始数字值 const newFormattedValue = inputElement.value.slice(0, caretStart) + inputElement.value.slice(caretStart + 1); const newOriginalValue = newFormattedValue.replace(/[^0-9-]/g, ''); setValue(newOriginalValue); // 光标保持在当前位置 setTimeout(() => { inputElement.setSelectionRange(caretStart, caretStart); }, 0); return; } } // 限制输入只能是数字和负号 if (!/^-?\d*$/g.test(value + e.key) && !['-', 'Backspace', 'Delete'].includes(e.key)) { e.preventDefault(); } }; const onChange = (e) => { const inputValue = e.target.value.replace(/[^0-9-]/g, ''); setValue(inputValue); const inputElement = inputRef.current; // 计算光标偏移量:新格式化字符串的分隔符数量减去旧的 const oldSeparatorCount = formatValue(value).match(/,/g)?.length || 0; const newSeparatorCount = formatValue(inputValue).match(/,/g)?.length || 0; const caretPosition = Math.max(0, inputElement.selectionStart + (newSeparatorCount - oldSeparatorCount)); inputElement.setSelectionRange(caretPosition, caretPosition); }; const formatValue = (value) => (value ? value.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : ''); return ( <input ref={inputRef} placeholder='Type Number' type='text' onKeyDown={onKeyDown} onChange={onChange} value={formatValue(value)} /> ); }; export default NumericInput;
关键修改点:
- Backspace逻辑:检测到前一个字符是分隔符时,阻止默认行为,计算原始值对应的位置并更新状态,最后调整光标到删除后的位置。
- Delete逻辑:检测到后一个字符是分隔符时,阻止默认行为,移除输入框中的分隔符并提取新的原始数字值更新状态,光标保持当前位置。
- 优化输入限制规则,避免非法字符输入。
二、更简洁的实现方式
手动处理光标和格式化容易出错,推荐两种简化方案:
1. 使用第三方库:react-number-format
这是专门用于数字输入格式化的React库,内置千分位、货币等格式化规则,自动处理光标和删除逻辑:
import React from 'react'; import NumberFormat from 'react-number-format'; const NumericInput = () => { return ( <NumberFormat placeholder='Type Number' thousandSeparator={true} allowNegative={true} type='text' /> ); }; export default NumericInput;
该库支持自定义分隔符、前缀后缀、精度控制等功能,完全无需手动处理光标和删除逻辑,代码简洁可靠。
2. 封装自定义Hook简化逻辑
如果不想依赖第三方库,可将核心逻辑封装成Hook,提升代码复用性:
import React, { useState, useRef, useCallback } from 'react'; const useNumberFormatter = (initialValue = '') => { const [rawValue, setRawValue] = useState(initialValue.replace(/[^0-9-]/g, '')); const inputRef = useRef(null); const formatValue = useCallback((value) => { return value ? value.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : ''; }, []); const handleKeyDown = useCallback((e) => { const elem = inputRef.current; const start = elem.selectionStart; const end = elem.selectionEnd; // Backspace删除分隔符 if (e.key === 'Backspace' && start === end && start > 0) { const prevChar = elem.value.charAt(start - 1); if (prevChar === ',') { e.preventDefault(); const sepCount = elem.value.slice(0, start - 1).match(/,/g)?.length || 0; const rawPos = start - 1 - sepCount; setRawValue(prev => prev.slice(0, rawPos) + prev.slice(rawPos + 1)); setTimeout(() => elem.setSelectionRange(start - 1, start - 1), 0); } } // Delete删除分隔符 if (e.key === 'Delete' && start === end && end < elem.value.length) { const nextChar = elem.value.charAt(start); if (nextChar === ',') { e.preventDefault(); const newFormatted = elem.value.slice(0, start) + elem.value.slice(start + 1); setRawValue(newFormatted.replace(/[^0-9-]/g, '')); setTimeout(() => elem.setSelectionRange(start, start), 0); } } // 输入限制 if (!/^-?\d*$/g.test(rawValue + e.key) && !['-', 'Backspace', 'Delete'].includes(e.key)) { e.preventDefault(); } }, [rawValue]); const handleChange = useCallback((e) => { const input = e.target.value.replace(/[^0-9-]/g, ''); setRawValue(input); const elem = inputRef.current; const oldSepCount = formatValue(rawValue).match(/,/g)?.length || 0; const newSepCount = formatValue(input).match(/,/g)?.length || 0; const caretPos = Math.max(0, elem.selectionStart + (newSepCount - oldSepCount)); setTimeout(() => elem.setSelectionRange(caretPos, caretPos), 0); }, [rawValue, formatValue]); return { inputRef, formattedValue: formatValue(rawValue), handleKeyDown, handleChange }; }; const NumericInput = () => { const { inputRef, formattedValue, handleKeyDown, handleChange } = useNumberFormatter(''); return ( <input ref={inputRef} placeholder='Type Number' type='text' onKeyDown={handleKeyDown} onChange={handleChange} value={formattedValue} /> ); }; export default NumericInput;
将格式化、光标处理、输入限制逻辑封装成Hook后,组件代码更简洁,逻辑复用性更强。
内容的提问来源于stack exchange,提问作者Digitalwolf
相关产品推荐
相关产品推荐

