useEffect内部变量是否必须全部列为依赖?场景矛盾如何解决?
调和React useEffect依赖规则与业务需求的矛盾
这个问题问得特别好——很多人刚接触React Hooks的时候都会被依赖数组的规则搞懵:到底是不是effect里用到的所有变量都必须列进依赖项?其实答案是:原则上应该遵守,但在你这种有特殊业务需求的场景下,我们可以用合理的技巧来调和矛盾,同时避开过时闭包的坑。
先拆解你的核心需求:
- 仅当
selectedArticle变化时,执行滚动逻辑 - 滚动逻辑需要用到当前最新的
isDesktop值 - 绝对不能在
isDesktop变化时触发滚动
直接把isDesktop加入依赖数组显然会违背第三个需求,但如果完全忽略它,又会陷入过时闭包的问题(比如isDesktop已经变成true了,但effect里拿到的还是旧的false值)。这里推荐用useRef来解决这个问题:
解决方案:用useRef跟踪最新的isDesktop值
import { useEffect, useRef } from 'react'; const scrollToTop = () => window.scrollTo(0, 0) const scrollToTopOfArticle = () => window.scrollTo(0, 200) function App({ isDesktop, selectedArticle }) { // 用ref保存isDesktop的最新值 const isDesktopRef = useRef(isDesktop); // 专门更新ref的effect:isDesktop变化时同步更新ref,但不触发滚动 useEffect(() => { isDesktopRef.current = isDesktop; }, [isDesktop]); // 滚动逻辑的effect:只依赖selectedArticle,执行时取ref的最新值 useEffect(() => { isDesktopRef.current ? scrollToTop() : scrollToTopOfArticle(); }, [selectedArticle]) return ( <div className="App"> <h1>{selectedArticle.title}</h1> <p>{selectedArticle.body}</p> </div> ); }
为什么这个方案可行?
- 满足业务需求:滚动逻辑只在
selectedArticle变化时触发,isDesktop变化不会触发任何滚动操作; - 避免过时闭包:通过
isDesktopRef.current总能拿到最新的isDesktop值,不会出现滚动逻辑用旧值的bug; - 不违背依赖规则的核心:React要求依赖数组的本质是确保effect能获取到最新的变量值,我们通过ref间接实现了这一点,而不是粗暴地忽略规则。
不推荐的方案:直接禁用ESLint警告
有些开发者可能会直接加注释忽略ESLint的依赖检查:
useEffect(() => { // eslint-disable-next-line react-hooks/exhaustive-deps isDesktop ? scrollToTop() : scrollToTopOfArticle() }, [selectedArticle])
这种方式非常不推荐——因为如果isDesktop发生了变化,下一次selectedArticle更新时,effect里的isDesktop还是旧的闭包值,会导致滚动逻辑不符合预期,埋下很难排查的bug。
总结
依赖数组的规则是为了帮我们避免过时闭包的问题,但不是僵化的教条。当业务需求和规则冲突时,我们需要找到既能满足需求,又能保证代码正确性的方法——useRef就是这种场景下的标准解决方案。
内容的提问来源于stack exchange,提问作者Chris Halley
相关产品推荐
相关产品推荐

