Next.js中如何正确结合useEffect与setInterval?计数器异常问题
在Next.js中解决useEffect结合setInterval导致计数器加速的问题
问题原因分析
你的代码存在几个核心问题,导致计数器加速和随机递增:
- useEffect使用async回调:useEffect的回调函数不能是async类型——async函数返回的是Promise,而effect要求清理函数必须是普通函数,这会导致原有的
clearInterval无法被正确执行,旧定时器得不到清理,多个定时器同时运行就会让计数器加速。 - 全局变量key:全局变量会在组件多次渲染时被共享修改,导致SWR的请求key频繁变化,触发多次数据请求,进而多次执行useEffect创建新的定时器。
- 依赖项不合理:将
router作为useEffect的依赖项,router对象的引用可能频繁变化,导致effect重复执行,创建多个定时器。 - 未正确导入useRouter:代码中使用了
router但没有导入,这会导致运行错误,间接引发状态异常。
修正后的完整代码
import useSWR from "swr"; import { useEffect, useState, useRef } from "react"; import { useRouter } from "next/router"; const fetcher = (...args) => fetch(...args).then((res) => res.json()); export default function App() { const router = useRouter(); const [timer, setTimer] = useState(0); const [info, setInfo] = useState({}); const timerRef = useRef(null); const [key, setKey] = useState(""); // 单独监听路由参数变化,更新请求key useEffect(() => { if (router.query?.id) { setKey(router.query.id); } }, [router.query]); // SWR请求:仅当key存在时发起请求 const { data, error } = useSWR(key ? `/api/getInfo?key=${key}` : null, fetcher); // 管理定时器与数据更新 useEffect(() => { // 创建定时器并保存到ref timerRef.current = window.setInterval(() => { setTimer((prevTime) => prevTime + 1); }, 1000); // 处理服务器返回的数据 if (data) { console.log("Data fetched from server>>>>", data); setInfo(data.info); } // 清理函数:确保每次effect执行前清除旧定时器 return () => { if (timerRef.current) { window.clearInterval(timerRef.current); timerRef.current = null; } }; }, [data]); return ( <> <p>Counter - {timer} </p> <p>Data from Server - {JSON.stringify(info)} </p> </> ); }
关键修改说明
- 移除async的useEffect回调:effect回调改为普通函数,内部如需异步操作可单独定义async函数调用。
- 用组件state替代全局变量key:避免全局状态污染,确保组件内状态独立可控,路由参数变化时通过单独的useEffect更新key。
- 使用useRef保存定时器ID:解决闭包问题,确保清理函数能访问到最新的定时器ID,彻底清除旧定时器。
- 拆分useEffect职责:将路由监听、定时器管理、数据请求拆分为独立的逻辑块,避免单一effect承担过多职责导致的重复执行。
- 修正SWR请求触发条件:仅当key存在时发起请求,避免无效的空请求触发不必要的effect执行。
- 修复数据显示问题:使用
JSON.stringify将info对象转为字符串,避免直接显示对象出现[object Object]的情况。
内容的提问来源于stack exchange,提问作者Anirudh
相关产品推荐
相关产品推荐

