使用useCallback后组件仍重渲染?自定义钩子重复执行原因及解决
问题描述
我有一个用户页面,通过自定义钩子useFetchUserId()获取用户信息。该钩子使用useEffect请求数据,即便我使用了useCallback钩子,每次切换标签页时,钩子中的console.log仍会打印。请解释原因并给出修复方案?
UserProfile页面代码:
const location = useLocation(); const userId = location.state?.userId; const [tab, setTab] = useState("1"); const { user, loading } = useFetchUserId(userId); const handleTabChange = useCallback(function handleTabChange(event, newTab) { setTab(newTab); }, []); return ( <TabList onChange={handleTabChange} aria-label="lab API tabs example"> <Tab label="Info" value="1" /> <Tab label="Cambia Password" value="2" /> <Tab label="Curriculum" value="3" /> </TabList> );
useFetchUserId钩子代码:
export const useFetchUserId = (id) => { const [user, setUser] = useState([]); const [loading, setLoading] = useState(false); console.log("fetch " + id); useEffect(() => { const fetch = async () => { try { const response = await axios.... const result = response?.data; if (result) { setUser(result); } } catch (err) { console.log(err); } }; fetch(); }, []); return { user, loading }; };
原因分析
- 组件重渲染触发钩子执行:切换标签页时调用
setTab更新了tab状态,导致UserProfile组件重渲染。自定义钩子useFetchUserId是在组件内部调用的,组件每重渲染一次,钩子的顶层代码(包括那个console.log)就会同步执行一次。useCallback只是缓存了handleTabChange函数,避免它每次重渲染都重新创建,但完全无法阻止组件本身的重渲染,自然拦不住钩子的执行。 - 钩子顶层代码的执行逻辑:React自定义钩子本质是复用的组件逻辑,钩子的顶层代码会在每次调用它的组件重渲染时同步运行,和
useEffect的异步执行逻辑无关。你把console.log放在钩子顶层,自然会随组件重渲染反复打印。 - useEffect依赖缺失:当前
useEffect的依赖数组为空,不仅会导致userId变化时无法重新请求数据,而且和本次console.log重复打印的问题无关——因为console.log不在useEffect里。
修复方案
1. 把日志移到useEffect内,仅在请求触发时打印
将console.log放到useEffect内部,这样只有当useEffect执行(即请求触发)时才会打印,同时补全useEffect的依赖项,确保userId变化时能重新请求数据:
export const useFetchUserId = (id) => { const [user, setUser] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { console.log("fetch " + id); // 仅在请求触发时打印 const fetchUser = async () => { setLoading(true); try { const response = await axios.get(`/api/users/${id}`); // 补全实际请求地址 const result = response?.data; if (result) { setUser(result); } } catch (err) { console.log(err); } finally { setLoading(false); } }; if (id) { // 避免id为空时发起无效请求 fetchUser(); } }, [id]); // 依赖id,id变化时重新执行请求 return { user, loading }; };
2. 可选:优化组件重渲染,减少钩子不必要的执行
如果UserProfile组件的重渲染过于频繁,可以用React.memo包裹组件,通过浅比较props避免无关状态变化导致的重渲染:
const UserProfile = React.memo(() => { // 原组件代码 });
注意:如果组件接收的props是复杂对象,可能需要自定义比较函数作为React.memo的第二个参数。
内容的提问来源于stack exchange,提问作者Giovanni
相关产品推荐
相关产品推荐

