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

React中useMemo/useCallback结合setState引发状态重置问题咨询

问题分析与解决方案

你遇到的这个问题核心在于useCallback的依赖项设置错误,加上闭包捕获了过时的state值,导致每次更新状态时都是基于组件初始渲染的空state进行扩展,自然会把之前已经输入的字段值给覆盖掉。

为什么useReducer没问题但setState出问题?

useReducer的更新逻辑是直接基于当前最新的state来处理的,它不会像你现在的setState写法那样被闭包捕获旧值。而你用useCallback包裹的setState,在依赖项为空数组的情况下,函数内部的state永远是组件第一次渲染时的初始值,不会随着组件重渲染而更新。

两种修复方案

方案1:使用setState的函数式更新(推荐)

React的setState支持传入一个函数,这个函数会接收当前最新的state作为参数,这样就不需要依赖外部的state变量,useCallback的依赖数组可以保持为空,函数也只会在初始渲染时创建一次:

const updateForm = useCallback(
  (field, value) => {
    // 用prevState获取最新状态,再扩展更新目标字段
    setState(prevState => ({...prevState, [field]: value }))
  }, [])

方案2:更新useCallback的依赖项

把state加入useCallback的依赖数组,这样每次state更新时,useCallback都会生成新的函数,确保函数内部能获取到最新的state:

const updateForm = useCallback(
  (field, value) => {
    setState({...state, [field]: value })
  }, [state])

不过这个方案的缺点是,每次state变化时updateForm都会重新创建,可能会导致子组件(即使加了React.memo)触发额外重渲染,所以优先推荐方案1。

修改后的完整代码

const {useState, useCallback} = React;
const CustomInputBox = React.memo(({label, id, value, onChange}) => {
  console.log(`${label} rendered`);
  return (
    <div className="custom-wrapper">
      <label>{label}</label>
      <input type="text" value={value} onChange={event => onChange(id, event.target.value)} />
    </div>
  )
})
const CustomSelect = React.memo(({label, id, value, onChange}) => {
  console.log(`${label} select rendered`);
  return (
    <div className="custom-wrapper">
      <label>{label}</label>
      <select name="cars" onChange={event => onChange(id, event.target.value)}>
        <option value="volvo">Volvo</option>
        <option value="saab">Saab</option>
        <option value="mercedes">Mercedes</option>
        <option value="audi">Audi</option>
      </select>
    </div>
  )
})
const FormApp = ({}) => {
  const [state, setState] = useState({ name : '', age: '', car: '' });
  // 使用函数式更新解决闭包问题
  const updateForm = useCallback(
    (field, value) => {
      setState(prevState => ({...prevState, [field]: value }))
    }, [])
  return (
    <div>
      <CustomInputBox label="Name" id="name" key="name" value={state.name} onChange={updateForm} />
      <CustomInputBox label="Age" id="age" key="age" value={state.age} onChange={updateForm} />
      <CustomSelect label="Car type" id="car" value={state.car} onChange={updateForm} />
    </div>
  );
}
ReactDOM.render(
  <div className="App">
    <main>
      <FormApp />
    </main>
  </div>,
  document.getElementById('root')
);

额外说明

  • 你用React.memo包裹子组件的做法是正确的,它能避免子组件在props没有变化时的不必要重渲染。
  • 函数式更新是React中处理闭包过时问题的常用手段,尤其在配合useCallback、useEffect这类依赖项钩子时,能有效保证状态更新的准确性。

内容的提问来源于stack exchange,提问作者Joe Kinden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 19:52:33