React函数组件状态更新后获取异常及焦点切换问题修复
问题:输入框自动切换焦点失效,state读取异常
需求
当用户在<input />中输入值时,自动切换焦点到下一个输入框。
问题现象
在refOne对应的输入框中输入值时,checkFocus函数内的codeOne始终为空,即便用setTimeout延迟读取也无效;但组件render阶段的console.log('render codeOne', codeOne)能正常输出最新值。
代码示例
import React, { useState, ChangeEvent, useRef, useEffect } from 'react'; function ModalPhoneBox() { const refOne = useRef<HTMLInputElement>(null); const refTwo = useRef<HTMLInputElement>(null); const refThree = useRef<HTMLInputElement>(null); const [codeData, setCodeData] = useState<FormInputType>({ codeOne: '', codeTwo: '', codeThree: '', }); const { codeOne, codeTwo, codeThree } = codeData; const checkFocus = () => { console.log('checkFocus'); setTimeout(() => { if ( refOne.current != null && refTwo.current != null && refThree.current != null ) { console.log('0'); console.log('codeOne', codeOne); if (codeOne && codeTwo === '') { console.log('1'); refTwo.current.focus(); } if (codeOne && codeTwo && codeThree === '') { console.log('2'); refThree.current.focus(); } } }, 1000); }; console.log('render codeOne', codeOne); return ( <div className={styles.inputGroup}> <div className={styles.inputBox}> <input ref={refOne} maxLength={1} onChange={(v: ChangeEvent<{ value: string }>) => { setCodeData({ ...codeData, codeOne: v.target.value }); checkFocus(); }} type="text" placeholder={''} /> </div> <div className={styles.inputBox}> <input ref={refTwo} maxLength={1} onChange={(v: ChangeEvent<{ value: string }>) => { setCodeData({ ...codeData, codeTwo: v.target.value }); checkFocus(); }} type="text" placeholder={''} /> </div> <div className={styles.inputBox}> <input ref={refThree} maxLength={1} onChange={(v: ChangeEvent<{ value: string }>) => setCodeData({ ...codeData, codeThree: v.target.value }) } type="text" placeholder={''} /> </div> </div> ); } export default ModalPhoneBox;
原因分析
这是闭包陷阱导致的:checkFocus函数在组件每次渲染时创建,它会捕获当前渲染周期的codeOne、codeTwo等值。当你调用setCodeData更新state后,组件会重新渲染生成新的checkFocus,但你在onChange里调用的是旧渲染周期的checkFocus——它只能访问旧的state值,所以即便等1秒,拿到的还是之前的空值。
修复方案
方案一:直接从DOM读取值(最直接)
既然已经有输入框的ref,直接通过ref.current.value获取最新值,完全不需要依赖state:
const checkFocus = () => { // 不需要1秒延迟,0秒延迟即可保证DOM更新完成 setTimeout(() => { if (refOne.current && refTwo.current && refThree.current) { const valOne = refOne.current.value; const valTwo = refTwo.current.value; const valThree = refThree.current.value; console.log('codeOne', valOne); if (valOne && valTwo === '') { refTwo.current.focus(); } if (valOne && valTwo && valThree === '') { refThree.current.focus(); } } }, 0); };
方案二:用useRef同步最新state
如果必须依赖state而非DOM,可以用useRef实时同步state的最新值:
// 新增一个ref保存最新的state const codeDataRef = useRef<FormInputType>(codeData); // 每次state更新时同步到ref中 useEffect(() => { codeDataRef.current = codeData; }, [codeData]); const checkFocus = () => { setTimeout(() => { if (refOne.current && refTwo.current && refThree.current) { // 从ref中读取最新的state值 const { codeOne, codeTwo, codeThree } = codeDataRef.current; console.log('codeOne', codeOne); if (codeOne && codeTwo === '') { refTwo.current.focus(); } if (codeOne && codeTwo && codeThree === '') { refThree.current.focus(); } } }, 0); };
额外优化:简化切换逻辑
可以把onChange逻辑抽成通用函数,直接在输入完成后立即切换焦点,完全去掉checkFocus:
const handleInputChange = (field: keyof FormInputType, nextRef: React.RefObject<HTMLInputElement>) => { return (v: ChangeEvent<{ value: string }>) => { // 使用函数式更新,避免依赖旧state setCodeData(prev => ({ ...prev, [field]: v.target.value })); // 输入非空时直接切换到下一个输入框 if (v.target.value) { nextRef.current?.focus(); } }; };
然后在输入框中使用:
<input ref={refOne} maxLength={1} onChange={handleInputChange('codeOne', refTwo)} type="text" placeholder="" /> <input ref={refTwo} maxLength={1} onChange={handleInputChange('codeTwo', refThree)} type="text" placeholder="" />
内容的提问来源于stack exchange,提问作者Morton
相关产品推荐
相关产品推荐

