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

无需useRef:在useMemo的useCallback中获取最新状态的最优方案

不触发子组件重渲染同时获取父组件最新状态的替代方案

你需要在父组件无关状态(count)变化时,子组件不重渲染,但回调函数能获取到最新的count值,除了useRef外,还有以下几种可行方案:

方案1:利用useState函数式更新读取最新状态

这是最简洁的方案,不需要额外依赖,借助setCount的函数式参数获取最新状态,同时不修改状态:

export default function App() {
  const [count, setCount] = useState(0);
  const onEditCallback = useCallback((name: string) => {
    // 通过setCount的函数式参数拿到最新prevCount
    setCount(prevCount => {
      console.log(name, prevCount);
      return prevCount; // 不修改状态,仅读取最新值
    });
  }, []);

  const renderMyChildComponent = useMemo(() => {
    return (
      <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} />
    );
  }, [onEditCallback]);

  return (
    <div className="App">
      <h2>{count}</h2>
      <Button
        variant="primary"
        className="mr-1"
        onClick={() => setCount(prev => prev + 1)} // 这里也建议用函数式更新避免闭包问题
      >
        Click me to increment
      </Button>
      {renderMyChildComponent}
    </div>
  );
}

核心逻辑:setCount的函数式参数会接收当前最新的状态值,我们在这个函数内部读取该值后返回原状态,既拿到了最新count,又不会触发状态更新。由于onEditCallback的依赖数组为空,不会随count变化而更新,子组件也就不会重渲染。

方案2:用useReducer管理状态

把状态迁移到useReducer,利用稳定的dispatch函数触发包含最新状态的操作:

// 定义action类型
type Action = 
  | { type: 'INCREMENT' }
  | { type: 'LOG_COUNT'; payload: string };

// reducer处理逻辑
const reducer = (state: { count: number }, action: Action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'LOG_COUNT':
      // 在reducer里直接访问最新的state.count
      console.log(action.payload, state.count);
      return state;
    default:
      return state;
  }
};

export default function App() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  const onEditCallback = useCallback((name: string) => {
    // 仅触发日志action,依赖的dispatch是稳定的
    dispatch({ type: 'LOG_COUNT', payload: name });
  }, [dispatch]);

  const renderMyChildComponent = useMemo(() => {
    return (
      <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} />
    );
  }, [onEditCallback]);

  return (
    <div className="App">
      <h2>{state.count}</h2>
      <Button
        variant="primary"
        className="mr-1"
        onClick={() => dispatch({ type: 'INCREMENT' })}
      >
        Click me to increment
      </Button>
      {renderMyChildComponent}
    </div>
  );
}

核心逻辑:useReducer返回的dispatch函数是稳定的,不会随状态变化而更新,因此onEditCallback的依赖不会改变,子组件不会重渲染。所有需要访问最新状态的逻辑都放在reducer中,确保能拿到最新的count值。

方案3:React.memo配合稳定回调优化子组件

如果子组件是纯组件,用React.memo包裹子组件,同时保持回调稳定,就不需要用useMemo包裹子组件渲染:

首先修改子组件:

// 用React.memo包裹,浅比较props
const ChildComponent = React.memo(({ name, onEditButtonClicked }) => {
  return <button onClick={() => onEditButtonClicked(name)}>Edit</button>;
});

然后父组件:

export default function App() {
  const [count, setCount] = useState(0);
  const onEditCallback = useCallback((name: string) => {
    // 同样用方案1的方式获取最新count
    setCount(prevCount => {
      console.log(name, prevCount);
      return prevCount;
    });
  }, []);

  return (
    <div className="App">
      <h2>{count}</h2>
      <Button
        variant="primary"
        className="mr-1"
        onClick={() => setCount(prev => prev + 1)}
      >
        Click me to increment
      </Button>
      <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} />
    </div>
  );
}

核心逻辑:React.memo会对比子组件的props变化,由于onEditCallback稳定、name是固定值,父组件count变化时,子组件props没有变化,因此不会重渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 18:39:58