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

useCallback未按预期工作:主题切换仅生效一次问题求助

问题:useCallback包裹主题切换函数后仅能触发一次切换

我希望将useCallback包裹的函数作为属性传递给子组件时,既能正常工作,又能避免每次调用时重新创建函数。在大型应用中遇到此问题,制作了可复现的小型示例:

  • 未使用useCallback时,应用能正常切换主题
  • 取消注释useCallback代码后,主题仅能切换一次,之后无法再触发
  • 预期目标:使用useCallback后既能正常切换主题,又能避免AppMenu.js在每次主题切换点击时重新渲染

/pages/index.js代码如下:

import {useCallback, useContext} from "react";
import AppMenu from "../src/AppMenu";
import { ThemeContext, ThemeProvider } from "../src/ThemeContext";

function Inner() {
  const { toggleTheme, darkTheme } = useContext(ThemeContext);

  return (
    <div>
      <h1>HOME</h1>
      {/*<AppMenu toggleTheme={useCallback(toggleTheme,[])} />*/}
      <AppMenu toggleTheme={toggleTheme} />
      <h2>darkTheme: {darkTheme === true ? "true" : "false"}</h2>
    </div>
  );
}

export default function Home() {
  return (
    <ThemeProvider>
      <Inner />
    </ThemeProvider>
  );
}

问题原因与解决方案

问题根源

你在Inner组件里用useCallback(toggleTheme, [])时,依赖数组为空,这会让useCallback缓存第一次渲染时的toggleTheme函数。而这个缓存的函数通过闭包捕获的是初始的darkTheme状态,后续主题更新后,它依然会使用旧状态值进行判断,所以只能完成一次切换。

正确处理方式

  1. 在ThemeProvider内用useCallback封装toggleTheme
    不要在子组件中包裹函数,而是在提供上下文的ThemeProvider内部创建toggleTheme时就用useCallback,并使用函数式更新确保状态正确:

    // 在ThemeContext的实现文件中
    import { useState, useCallback, createContext } from 'react';
    
    export const ThemeContext = createContext();
    
    export function ThemeProvider({ children }) {
      const [darkTheme, setDarkTheme] = useState(false);
      // 用useCallback包裹,依赖数组为空也安全,因为用了函数式更新
      const toggleTheme = useCallback(() => {
        setDarkTheme(prevTheme => !prevTheme);
      }, []);
    
      return (
        <ThemeContext.Provider value={{ darkTheme, toggleTheme }}>
          {children}
        </ThemeContext.Provider>
      );
    }
    
  2. 给AppMenu添加React.memo优化渲染
    确保AppMenu组件在props不变时不重新渲染,只需要给它包裹一层React.memo:

    // AppMenu.js文件中
    import React from 'react';
    
    const AppMenu = React.memo(({ toggleTheme }) => {
      return (
        <button onClick={toggleTheme}>切换主题</button>
      );
    });
    
    export default AppMenu;
    
  3. 子组件直接使用上下文提供的toggleTheme
    此时Inner组件里直接从上下文拿到的toggleTheme是稳定的引用,不需要再用useCallback包裹,直接传给AppMenu即可:

    // Inner组件中
    <AppMenu toggleTheme={toggleTheme} />
    

为什么原写法失效?

原写法在Inner组件中缓存了第一次获取的toggleTheme,这个函数内部的闭包已经固定了初始的darkTheme值,后续状态更新后,缓存的函数无法感知到新状态,自然无法继续触发正确的切换逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 15:20:33