为何自定义React Hook的dispatch无法在空依赖useEffect中调用?
嗨,这个问题其实戳中了React Hooks依赖追踪的核心细节,我来给你理清楚两者的差异和原因:
一、原生useState的setter为什么可以不用加依赖?
React官方已经明确保证:useState返回的setter函数在组件的整个生命周期中引用都是不变的。也就是说,不管组件渲染多少次,setFlag(你的例子里的)的引用始终是同一个。
ESLint的react-hooks/exhaustive-deps规则是知道这个特性的,所以它会自动忽略对useState setter的依赖检查——这就是为什么你写:
useEffect(() => { setFlag(true); }, []);
的时候,不会收到任何警告或错误。
二、自定义Hook返回的dispatch为什么被要求加依赖?
来看你的自定义Hook代码:
function useCustomHook() { const [success, setSuccess] = useState(false); const component = <div>{ success ? "Success" : "Fail" }</div>; const dispatch = useCallback(success => { setSuccess(success); }, []); return [component, dispatch]; }
你用useCallback包裹了setSuccess,并且给useCallback传了空依赖数组。虽然setSuccess本身是稳定的,但ESLint没办法“看透”你的自定义Hook内部逻辑——它只能在当前组件的层面分析变量:
- 在你的业务组件里,
setCustomFlag是自定义Hook返回的一个函数,ESLint不知道这个函数的引用会不会随着组件渲染而变化。 - 虽然你现在的useCallback依赖是空的,看起来
dispatch的引用是稳定的,但如果以后自定义Hook的逻辑变化(比如依赖了其他状态或props),这个dispatch就可能变成过时的闭包,导致难以排查的bug。
所以ESLint会强制要求你把setCustomFlag加到useEffect的依赖数组里,这是它的“安全兜底”逻辑。
怎么解决这个问题?
有两种简单的方式:
1. 直接返回原生的setSuccess,不用useCallback包裹
既然setSuccess本身已经是稳定的,完全没必要多一层useCallback包裹。修改自定义Hook:
function useCustomHook() { const [success, setSuccess] = useState(false); const component = <div>{ success ? "Success" : "Fail" }</div>; return [component, setSuccess]; }
这样返回的setCustomFlag就是原生的setter,ESLint会自动识别它的稳定性,不会要求你加到依赖数组里,和你用原生useState的效果完全一致。
2. 给useCallback加上正确的依赖(推荐)
如果你确实需要用useCallback包裹(比如以后要扩展逻辑),记得把setSuccess加到useCallback的依赖数组里:
const dispatch = useCallback(success => { setSuccess(success); }, [setSuccess]);
因为setSuccess是稳定的,所以这个依赖数组不会导致dispatch的引用变化。之后在业务组件里把setCustomFlag加到useEffect的依赖数组里也没问题:
useEffect(() => { setCustomFlag(true); }, [setCustomFlag]);
这样既符合ESLint规则,也能避免潜在的闭包问题。
内容的提问来源于stack exchange,提问作者Nick Viatick

