为什么React Hooks的useEffect、useCallback依赖项要加入setCount
为什么 useState 返回的 setter 函数需要放入 Hooks 依赖数组
核心逻辑说明
- 首先要明确 React Hook 的基础依赖规则:所有在
useEffect、useCallback、useMemo回调内部用到的外部变量、函数,都必须列入依赖数组,这是react-hooks/exhaustive-depsESLint 校验插件的强制要求,漏加会抛出警告。 - 其次要知道
useState返回的 setter 函数的特性:React 官方明确保证,同一个useState调用返回的 setter 函数,在组件整个挂载生命周期内引用永远不会发生变化,所以把它加入依赖数组,永远不会触发useEffect重复执行、也不会导致useCallback/useMemo返回新的引用。
为什么不能省略这个稳定的依赖
很多人会觉得既然 setter 引用不会变,加不加都不影响运行,为什么还要强制加?主要有两个原因:
- 统一规则降低维护成本:ESLint 校验规则无法区分你用的函数是 React 原生的稳定 setter 还是自己封装的不稳定函数,统一要求所有用到的变量都进依赖数组,可以避免后续代码迭代时出现闭包旧值的隐性bug。
- 避免逻辑变更带来的问题:如果后续你把原生的 setter 替换成自定义封装的修改逻辑,只要你遵守了依赖规则,不需要改动依赖数组就能正常运行,反之如果之前省略了依赖,就会直接出现闭包问题。
代码示例说明
正常示例
import { useState, useEffect, useCallback } from 'react'; function Counter() { const [count, setCount] = useState(0); // 示例1:useEffect 中使用了 setCount useEffect(() => { // 模拟每3秒自动计数+1 const timer = setInterval(() => { setCount(prev => prev + 1); }, 3000); return () => clearInterval(timer); }, [setCount]); // 这里加入setCount不会导致effect重复执行,因为setCount引用永远不变,effect只会在组件挂载时运行一次 // 示例2:useCallback 中使用了 setCount const increment = useCallback(() => { setCount(prev => prev + 1); }, [setCount]); // 这里加入setCount可以保证increment引用永远稳定,避免传递给子组件时导致不必要的重渲染 return ( <div> <p>当前计数:{count}</p> <button onClick={increment}>手动+1</button> </div> ); }
反例说明(省略依赖的风险)
如果你使用的是自定义封装的修改函数,不是原生的useState setter,省略依赖就会直接出现问题:
import { useState, useCallback } from 'react'; // 自定义封装的计数Hook,返回的修改函数引用不稳定 function useCustomCount() { const [count, setCount] = useState(0); // 每次组件重渲染都会生成新的customSetCount函数 const customSetCount = (val) => { console.log('触发计数修改'); setCount(val); } return [count, customSetCount]; } function Counter() { const [count, setCount] = useCustomCount(); const increment = useCallback(() => { setCount(prev => prev + 1); }, []); // 省略了setCount依赖,调用的永远是第一次渲染的旧customSetCount // 如果后续customSetCount逻辑依赖了其他变量,就会出现闭包旧值的bug return <button onClick={increment}>手动+1</button>; }
内容的提问来源于stack exchange,提问作者Azizjon Nuryagdyyev
相关产品推荐
相关产品推荐

