React Hooks中如何合规实现componentDidMount以满足eslint exhaustive-deps规则?
这是个非常典型的React Hooks使用误区,我来帮你把逻辑理清楚:
1. 依赖数组的本质:不止检测props
首先纠正一个误解:依赖数组不是只检测props的变化,它追踪的是所有在effect回调内部用到的、可能随组件渲染而变化的值——包括组件内定义的函数、state、props,甚至是父组件传递的回调。React的依赖追踪是基于值的引用/内容变化,不管这个值来自哪里。
2. 为什么getActiveUser必须加入依赖?
如果getActiveUser是定义在组件内部的函数(比如下面这种写法):
const MyComponent = () => { const getActiveUser = () => { // 发起API请求 }; useEffect(() => { getActiveUser(); }, []); // 这里lint会报错 }
每次组件渲染时,getActiveUser都会被重新创建,它的引用地址是全新的。如果依赖数组里不包含它,React会认为effect的依赖没有变化,不会重新执行effect,但实际上函数本身已经变了——这可能导致闭包问题(比如函数内部引用的state/props是旧的)。eslint的react-hooks/exhaustive-deps规则就是帮你提前发现这类潜在bug。
3. 加入依赖是否违背官方文档?
完全不违背!官方文档里提到的空依赖数组[],是针对effect内部没有用到任何会变化的值的场景(比如纯静态的初始化逻辑)。如果你的effect里用到了组件内的函数,就必须把它加入依赖数组——这才是符合文档“正确声明所有依赖”的要求。
4. 针对无props/id的API调用,优雅的处理方式
既然你的API调用不需要依赖任何props或id,有几种更优雅的写法可以兼顾lint规则和代码逻辑:
- 把函数移到组件外部:如果
getActiveUser不需要访问组件内的state/props,直接定义在组件外面,这样它的引用永远不会变,依赖数组可以保持为空:const getActiveUser = () => { // 发起API请求 }; const MyComponent = () => { useEffect(() => { getActiveUser(); }, []); // 这里lint不会报错 } - 用
useCallback缓存函数:如果函数必须定义在组件内部(比如要用到组件内的state),用useCallback包裹它,固定其引用:const MyComponent = () => { const [user, setUser] = useState(null); const getActiveUser = useCallback(async () => { const res = await fetch('/api/active-user'); setUser(await res.json()); }, []); // 这里的空数组表示函数不会依赖任何值,引用保持稳定 useEffect(() => { getActiveUser(); }, [getActiveUser]); // 加入依赖,但因为useCallback缓存了引用,effect只会执行一次 } - 直接把API逻辑写在effect内部:如果逻辑简单,没必要单独抽成函数,直接写在effect里,依赖数组为空即可:
const MyComponent = () => { useEffect(() => { const fetchUser = async () => { const res = await fetch('/api/active-user'); // 处理结果 }; fetchUser(); }, []); // 这里lint不会报错,因为effect内部的函数没有依赖外部变量 }
5. 总结:空依赖数组依然合法
react-hooks/exhaustive-deps规则并没有禁止空依赖数组,它只是要求你诚实地列出所有effect用到的依赖。空数组是完全合法的,但只有当effect内部真的没有用到任何会随渲染变化的值时,才适合使用。
内容的提问来源于stack exchange,提问作者KT-mongo

