You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 18:05:57