React+Next.js项目useEffect内定时器触发依赖缺失警告如何解决
问题根因
你碰到的是React Hooks的依赖检查规则告警,原因是useEffect回调中引用了外部变量pointsPerSecond,但你没把它加到useEffect的依赖数组里。虽然现在你没修改pointsPerSecond的时候运行正常,但如果后续你调整pointsPerSecond的值,闭包陷阱会导致定时器里拿到的永远是初始的2,逻辑就会出问题,eslint就是在提醒你规避这个风险。
方案1:补全依赖并增加定时器清理逻辑(最推荐)
如果后续你有修改pointsPerSecond的需求(比如买道具增加产出速度),用这个方案最稳妥,每次pointsPerSecond变化时会销毁旧定时器,创建使用最新值的新定时器:
import Head from 'next/head'; import { useEffect, useState } from 'react'; import Header from '../components/Header'; export default function Home() { const ticksPerSecond = 10; const [points, setPoints] = useState(10); const [pointsPerSecond, setPointsPerSecond] = useState(2); useEffect(() => { // 保存定时器ID,用于清理 const timer = setInterval(() => { setPoints((points) => points + pointsPerSecond / ticksPerSecond); }, 1000 / ticksPerSecond); // 组件卸载/依赖变化时清理旧定时器 return () => clearInterval(timer); }, [pointsPerSecond]); // 把用到的外部变量加到依赖数组 return ( <> <Header points={points} pointsPerSecond={pointsPerSecond} /> </> ); }
这个方案可以完全消除告警,且符合React Hooks的规范,后续修改pointsPerSecond也能自动生效。
方案2:用useReducer改造(适配复杂状态联动场景)
如果你的业务逻辑后续会更复杂,涉及多个状态联动,可以按eslint的提示用useReducer改造,reducer里可以拿到最新的所有状态,且dispatch是稳定引用,不需要加到依赖数组:
import Head from 'next/head'; import { useEffect, useState, useReducer } from 'react'; import Header from '../components/Header'; const ticksPerSecond = 10; // 定义reducer逻辑 function reducer(state, action) { switch (action.type) { case 'tick': return {...state, points: state.points + state.pointsPerSecond / ticksPerSecond}; case 'updatePps': return {...state, pointsPerSecond: action.payload}; default: return state; } } export default function Home() { const [state, dispatch] = useReducer(reducer, { points: 10, pointsPerSecond: 2 }); useEffect(() => { setInterval(() => { dispatch({type: 'tick'}); }, 1000 / ticksPerSecond); }, [dispatch]); // dispatch引用永远稳定,不会触发effect重运行 return ( <> <Header points={state.points} pointsPerSecond={state.pointsPerSecond} /> </> ); }
方案3:确认pointsPerSecond永不修改时的处理
如果你100%确认pointsPerSecond永远不会被修改,有两种方式可选:
- 把
pointsPerSecond提到组件外部定义,变成常量,就不会被eslint判定为需要加依赖:
const DEFAULT_PPS = 2; // 组件外定义常量 export default function Home() { const ticksPerSecond = 10; const [points, setPoints] = useState(10); const [pointsPerSecond, setPointsPerSecond] = useState(DEFAULT_PPS); useEffect(() => { setInterval(() => { setPoints((points) => points + DEFAULT_PPS / ticksPerSecond); }, 1000 / ticksPerSecond); }, []); return ( <> <Header points={points} pointsPerSecond={pointsPerSecond} /> </> ); }
- 手动禁用该行eslint检查(不推荐,除非你完全清楚风险):
useEffect(() => { setInterval(() => { setPoints((points) => points + pointsPerSecond / ticksPerSecond); }, 1000 / ticksPerSecond); // eslint-disable-next-line react-hooks/exhaustive-deps }, []);
内容的提问来源于stack exchange,提问作者Julian Lachniet
相关产品推荐
相关产品推荐

