React+TS中如何让自定义Hook返回的更新函数无需加入依赖数组?
问题:自定义Hook的更新函数触发ESLint依赖警告,如何让它像useState的setState一样免依赖检查?
在React + TypeScript环境中,我希望创建一个自定义Hook,使其返回的更新函数拥有稳定的引用(类似useState返回的setState或useDispatch返回的dispatch),从而不会被ESLint的react-hooks/exhaustive-deps规则要求加入依赖数组。
现有自定义Hook代码如下:
import { useRef, useState, useEffect, useCallback } from 'react'; export type SetStateWithCallback<T> = (newState: React.SetStateAction<T>, callback?: Function) => void; export function useStateWithCallback<T>(initState: T): [T, SetStateWithCallback<T>] { const [state, setState] = useState<T>(initState); const callbackRef = useRef<Function | null>(null); const setStateWithCallback = useCallback((newState: React.SetStateAction<T>, callback?: Function) => { callbackRef.current = typeof callback === 'function' ? callback : null; setState(newState); }, []); useEffect(() => { if (callbackRef.current) { callbackRef.current?.(state); callbackRef.current = null; } }, [state]); return [state, setStateWithCallback]; }
但使用该Hook时,ESLint仍会要求将返回的更新函数加入依赖数组,和内置setState的处理不同:
function MyTestComponent() { const [state, setState] = useState({}); const [customState, setCustomState] = useStateWithCallback({}); const test = useCallback(() => setState({}), []); // 无警告,正常 const testCustom = useCallback(() => setCustomState({}), []); // 警告:React Hook useCallback has a missing dependency: 'setCustomState'. Either include it or remove the dependency array. eslint(hooks/exhaustive-deps) return null; }
如何修改setCustomState,让它达到和setState相同的效果?
解决方案
ESLint的react-hooks/exhaustive-deps规则对React内置Hook返回的函数(如setState、dispatch)有特殊豁免,认为它们的引用始终稳定。但自定义Hook返回的函数,即使通过useCallback绑定了空依赖数组,规则仍会触发警告,因为它无法自动识别该函数的稳定性。
我们可以通过用useRef存储更新逻辑,并返回一个稳定的代理函数来解决这个问题,确保返回的更新函数引用永远不变,同时让ESLint认可其稳定性:
修改后的自定义Hook代码:
import { useRef, useState, useEffect, useCallback } from 'react'; export type SetStateWithCallback<T> = (newState: React.SetStateAction<T>, callback?: () => void) => void; export function useStateWithCallback<T>(initState: T): [T, SetStateWithCallback<T>] { const [state, setState] = useState<T>(initState); const callbackRef = useRef<(() => void) | null>(null); // 用ref存储更新逻辑,确保每次渲染都能拿到最新的逻辑 const updateStateRef = useRef<SetStateWithCallback<T>>(); // 更新ref中的逻辑(每次渲染都会执行,但ref的引用不变) updateStateRef.current = (newState, callback) => { callbackRef.current = callback || null; setState(newState); }; // 返回一个稳定的代理函数,依赖为空数组,引用永远不变 const setStateWithCallback = useCallback((...args) => { updateStateRef.current?.(...args); }, []); useEffect(() => { if (callbackRef.current) { callbackRef.current(); callbackRef.current = null; } }, [state]); return [state, setStateWithCallback]; }
原理说明
updateStateRef存储更新逻辑:每次组件渲染时,我们更新updateStateRef.current为最新的更新函数逻辑,但updateStateRef本身的引用在组件生命周期内始终不变。- 稳定的代理函数:通过
useCallback创建一个依赖为空的代理函数,它只负责调用updateStateRef.current的逻辑。这个代理函数的引用永远不会改变,符合React内置更新函数的特性。 - ESLint豁免:现在当你在
useCallback或useEffect中使用setStateWithCallback时,ESLint不会再要求将其加入依赖数组,因为它的引用是稳定的,且依赖数组为空的useCallback已经明确了这一点。
替代方案(快速解决)
如果你不想修改Hook实现,也可以在使用时通过ESLint注释直接禁用该警告:
const testCustom = useCallback(() => setCustomState({}), []); // eslint-disable-next-line react-hooks/exhaustive-deps
但这种方法不够优雅,推荐优先使用上面的Hook修改方案。
内容的提问来源于stack exchange,提问作者Gerson Goulart
相关产品推荐
相关产品推荐

