React useEffect Hook依赖项配置规则是什么?为何未传count也无异常?
众所周知,useEffect Hook是放置副作用相关逻辑的位置,我目前存在疑问:useEffect Hook的依赖数组中应该传入哪些依赖项?
React官方文档指出
如果使用该优化机制,请确保数组包含组件作用域内(例如props、state)会随时间变化、且被effect使用的所有值,否则你的代码会引用之前渲染周期的过期值。
来看如下示例:
export default function App() { const [count, setCount] = React.useState(0); React.useEffect(() => { console.log("component mounted"); }, []); const update = () => { for(let i=0; i<5;i++){ setCount(count+i) } }; return ( <div> {console.log(count)} {count} <button type="button" onClick={update}> Add </button> </div> ); }
按照上述文档的说法,我们应该将count变量作为依赖项传入useEffect,这会使得useEffect重新执行。
但在上述示例中,不传count依赖项并不会对UI造成任何问题:不存在过期值,变量符合预期更新,UI使用准确的值重新渲染,控制台也会输出当前值。
那么我的问题是:为什么要将count变量作为依赖项传入useEffect?当前UI明明运行正常?
补充说明
我已知useEffect的回调会在依赖数组中的值发生变化时触发,我的核心疑问是:到底哪些内容应该放入依赖数组?我们是否真的需要传入状态变量?
核心结论
你当前的示例里根本不需要把count放进这个useEffect的依赖数组,你的误解是把「组件所有状态变量」和「effect使用的变量」搞混了。
官方规则的核心从来不是「所有状态都要放依赖」,而是:只有被effect回调逻辑实际用到的、来自组件作用域的可变值,才需要放进依赖数组。
依赖数组的准入规则(同时满足才需要放)
- 该值来自组件作用域(包括props、state、组件内定义的变量/函数)
- 该值会随渲染周期发生变化
- 该值在useEffect的回调逻辑中被直接读取、引用
你示例的误区拆解
你给出的代码里,useEffect的回调只有一行console.log("component mounted"),全程没有读取、引用count的值,所以count完全不符合上面的准入规则,空依赖数组[]是完全正确的写法。
你觉得「应该放count」是误以为所有状态变量都要加,但实际上只要effect没用到,加了反而会导致effect不必要的重复执行。
为什么有的时候不加依赖会出bug?
只有当effect逻辑实际用到了某个可变值,却没把它放进依赖时,才会出现问题——而且这种bug经常不会直接体现在UI上:
举个错误示例:
useEffect(() => { // 这里直接用到了count const timer = setInterval(() => { console.log("当前count值:", count); }, 1000); return () => clearInterval(timer); }, []); // 错误:没把count放进依赖
这时候UI本身的渲染是完全正常的(点击按钮后页面显示的count会正确更新),但定时器的回调里拿到的永远是第一次渲染时的count(永远是0)——这就是官方文档说的「引用之前渲染周期的过期值」的问题。
如果把count放进依赖数组,每次count变化时,effect会先清理上一个定时器,再生成新的定时器,就能拿到最新的count值。
常见容易被遗漏的依赖
除了state、props之外,组件内定义的普通变量、函数,如果被effect用到,也必须放进依赖:
const [count, setCount] = useState(0); // 组件内定义的工具函数 const formatCount = () => `计数:${count}`; useEffect(() => { // 这里用到了formatCount document.title = formatCount(); }, []); // 错误:formatCount每次渲染都会重新生成,必须加进依赖
如果不想让频繁变化的函数触发effect重执行,可以用useCallback包裹函数,或者把函数定义移到effect内部。
内容的提问来源于stack exchange,提问作者Debarshi Bhattacharjee

