React自定义Hook的useEffect依赖警告及无限循环问题求解
解决React useEffect依赖警告与自定义Hook无限循环问题
你遇到的这个问题其实很常见——自定义Hook返回的函数在useEffect里使用时,ESLint的exhaustive-deps规则会揪出依赖缺失,但直接添加依赖又会触发无限循环。我来帮你拆解原因和解决办法:
问题根源
你的callApi函数是在useApi Hook每次调用时重新创建的(也就是组件每次渲染时,callApi都会是一个新的函数引用)。如果把它加入useEffect的依赖数组,React会认为依赖发生了变化,就会重新执行useEffect;而callApi内部又会更新组件状态,触发重新渲染,进而创建新的callApi,形成无限循环。
最优解决方案:用useCallback稳定callApi的引用
在你的自定义Hook里,用React.useCallback包裹callApi函数,这样它的引用只会在依赖的状态 setter 变化时才更新(而React保证useState返回的setter函数是永久稳定的,不会随渲染变化)。
修改后的useApi Hook代码:
import { useState, useCallback } from 'react' import axios, { AxiosRequestConfig } from 'axios' export default <DataType>() => { const [loading, setLoading] = useState(false) const [error, setError] = useState('') const [data, setData] = useState<DataType>() // 用useCallback缓存callApi,依赖稳定的setter函数 const callApi = useCallback(async (config: AxiosRequestConfig) => { setLoading(true) setError('') try { const response = await axios.request<DataType>(config) setData(response.data) } catch (error) { if (error.response) setError(error.response.data.msg) else if (error.request) setError('A network error occured') else setError('An error occured') } finally { setLoading(false) } }, [setLoading, setError, setData]) return { data, loading, error, callApi } }
然后在组件的useEffect里,放心把callApi加入依赖数组:
const { data: posts, loading, error, callApi } = useApi<Post[]>() useEffect(() => { callApi({ url: 'http://localhost:4000/blog' }) }, [callApi])
这样一来,callApi的引用始终稳定,useEffect只会在组件挂载时执行一次,同时完美满足ESLint的规则要求。
临时替代方案:忽略ESLint警告(不推荐)
如果你暂时不想修改自定义Hook,可以在useEffect的注释里禁用该规则,但这是治标不治本的办法,可能隐藏后续的闭包过期问题:
useEffect(() => { callApi({ url: 'http://localhost:4000/blog' }) }, []) // eslint-disable-line react-hooks/exhaustive-deps
当前代码的潜在风险
- 闭包过期问题:如果后续你在
callApi里加入了其他依赖变量(比如组件的props),忽略exhaustive-deps警告会导致callApi引用的是旧值,引发难以排查的bug。 - 意外的无限循环:如果不小心把
callApi加入依赖数组而没做稳定化处理,会直接触发无限渲染,严重影响应用性能。
内容的提问来源于stack exchange,提问作者simon47
相关产品推荐
相关产品推荐

