React 手机号格式化输入框修改内容后光标跳转至末尾如何解决
问题根因
你当前的实现每次触发输入事件时,都会直接将格式化后的完整字符串覆盖到input的value上,浏览器更新输入框内容后会默认将光标定位到内容末尾,不会保留你编辑时的原始光标位置,因此在中间增删字符时会出现光标跳转问题。
修复思路
- 用
useRef获取input的DOM实例,记录输入前的光标位置 - 计算光标位置之前的纯数字个数,匹配格式化后对应内容的光标偏移量
- 状态更新完成后,手动将光标重置到计算后的正确位置
修复后完整代码
import { useState, useRef } from "react"; function formatPhoneNumber(phone) { const area = phone.slice(0, 3); const first = phone.slice(3, 6); const second = phone.slice(6, 10); let formattedTelephoneNumber = phone; if (phone.length >= 1) { formattedTelephoneNumber = `(${formattedTelephoneNumber}`; } if (phone.length >= 4) { formattedTelephoneNumber = `(${area}) ${first}`; } if (phone.length >= 7) { formattedTelephoneNumber = `(${area}) ${first}-${second}`; } return formattedTelephoneNumber; } function getRawPhoneNumber(inputValue) { return [...inputValue].filter((char) => /\d/.test(char)).join(""); } // 计算格式化后正确的光标位置 function getCorrectCaretPosition(formattedValue, rawDigitsBeforeCaret) { let digitCount = 0; for (let i = 0; i < formattedValue.length; i++) { if (/\d/.test(formattedValue[i])) { digitCount++; } if (digitCount === rawDigitsBeforeCaret) { return i + 1; } } return formattedValue.length; } export default function App() { const [value, setValue] = useState(""); const inputRef = useRef(null); const handleInputChange = (e) => { const input = e.target; // 记录当前光标前的纯数字个数 const caretPosition = input.selectionStart; const valueBeforeCaret = input.value.slice(0, caretPosition); const rawDigitsBeforeCaret = getRawPhoneNumber(valueBeforeCaret).length; const phoneNumber = getRawPhoneNumber(input.value); const formatted = formatPhoneNumber(phoneNumber); setValue(formatted); // 等DOM更新完成后重置光标位置 requestAnimationFrame(() => { if (!inputRef.current) return; const newCaretPosition = getCorrectCaretPosition(formatted, rawDigitsBeforeCaret); inputRef.current.setSelectionRange(newCaretPosition, newCaretPosition); }); }; return ( <div className="App"> <input ref={inputRef} value={value} onChange={handleInputChange} /> </div> ); }
内容的提问来源于stack exchange,提问作者Joji
相关产品推荐
相关产品推荐

