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状态,后续主题更新后,它依然会使用旧状态值进行判断,所以只能完成一次切换。
正确处理方式
在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> ); }给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;子组件直接使用上下文提供的toggleTheme
此时Inner组件里直接从上下文拿到的toggleTheme是稳定的引用,不需要再用useCallback包裹,直接传给AppMenu即可:// Inner组件中 <AppMenu toggleTheme={toggleTheme} />
为什么原写法失效?
原写法在Inner组件中缓存了第一次获取的toggleTheme,这个函数内部的闭包已经固定了初始的darkTheme值,后续状态更新后,缓存的函数无法感知到新状态,自然无法继续触发正确的切换逻辑。
内容的提问来源于stack exchange,提问作者Peter Kellner
相关产品推荐
相关产品推荐

