You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS与JavaScript空闲时间检测异常,求优化方案

问题描述

需要实现用户空闲时间检测功能:当用户长时间未操作时自动弹出提示对话框,内容为“您已长时间未操作。请点击下方按钮保持会话活跃”;用户点击按钮后需重置空闲时间,避免触发登出操作。但当前实现中点击按钮后仍会执行登出,效果不符合预期,求更优实现方案。


现有实现代码

App 组件

import { Button, Dialog } from "@mui/material";
import DialogHeader from "./components/DialogHeader";
import DialogBody from "./components/DialogBody";
import DialogFooter from "./components/DialogFooter";
import { useIdleSession } from "./hooks/useIdleSession";

function App() {
  const handleLogout = () => {
    console.log("logout");
  };

  const IDLE_CONFIG = {
    threshold: 5000, // 空闲阈值(毫秒)
    timeout: 8000, // 提示后超时登出的时长(毫秒)
    action: handleLogout // 超时后执行的登出操作
  };

  const { open, idleTime, handleUserActivity } = useIdleSession(IDLE_CONFIG);

  return (
    <>
      <h1>HELLO WORLD</h1>
      <Dialog open={open}>
        <DialogHeader>
          您已空闲 {idleTime / 1000} 秒
        </DialogHeader>

        <DialogBody>
          您已长时间未操作。请点击下方按钮保持会话活跃
        </DialogBody>
        <DialogFooter>
          <Button
            color="primary"
            variant="contained"
            onClick={handleUserActivity}
          >
            保持会话活跃
          </Button>
        </DialogFooter>
      </Dialog>
    </>
  );
}

export default App;

useIdleSession 钩子

import { useEffect, useState, useCallback } from "react";

export const useIdleSession = (config) => {
  const [lastActive, setLastActive] = useState(new Date());
  const [open, setOpen] = useState(false);
  const [idleTime, setIdleTime] = useState(0);
  const [logoutTimeoutId, setLogoutTimeoutId] = useState(null);
  const isLoggedIn = true;

  // 处理用户活动的函数
  const handleUserActivity = useCallback(() => {
    setLastActive(new Date());
    setOpen(false);
    clearTimeout(logoutTimeoutId);
  }, [logoutTimeoutId]);

  useEffect(() => {
    if (isLoggedIn) {
      // 定时检查用户空闲状态
      const intervalId = setInterval(() => {
        const currentTime = new Date();
        const idle = currentTime - lastActive;
        // 超过空闲阈值时弹出提示
        if (idle > config.threshold) {
          setIdleTime(idle);
          setOpen(true);
          // 设置超时登出的定时器
          const newLogoutTimeoutId = setTimeout(config.action, config.timeout);
          setLogoutTimeoutId(newLogoutTimeoutId);
        }
      }, config.threshold);

      return () => {
        clearInterval(intervalId);
        clearTimeout(logoutTimeoutId);
      };
    }
  }, [isLoggedIn, lastActive, handleUserActivity, config, logoutTimeoutId]);

  return { open, idleTime, handleUserActivity };
};

问题分析

当前实现存在几个核心问题:

  • 重复创建登出定时器:每次interval触发时,只要用户处于空闲状态就会新建setTimeout,之前的定时器未被清除,导致点击保持活跃按钮后旧定时器仍会触发登出。
  • 未监听用户常规操作:仅通过interval检查空闲时间,无法实时响应用户鼠标移动、按键等操作,无法及时重置空闲状态。
  • 依赖项管理混乱:useEffect依赖logoutTimeoutId导致interval频繁重建,handleUserActivity依赖logoutTimeoutId导致函数频繁重创建。

优化实现方案

优化后的 useIdleSession 钩子

import { useEffect, useState, useCallback, useRef } from "react";

export const useIdleSession = (config) => {
  const [lastActive, setLastActive] = useState(new Date());
  const [open, setOpen] = useState(false);
  const [idleTime, setIdleTime] = useState(0);
  const logoutTimeoutRef = useRef(null);
  const checkIntervalRef = useRef(null);
  const isLoggedIn = true;

  // 统一重置空闲状态
  const resetIdleState = useCallback(() => {
    setLastActive(new Date());
    setOpen(false);
    // 清除登出定时器
    if (logoutTimeoutRef.current) {
      clearTimeout(logoutTimeoutRef.current);
      logoutTimeoutRef.current = null;
    }
  }, []);

  // 处理用户活动
  const handleUserActivity = useCallback(() => {
    resetIdleState();
  }, [resetIdleState]);

  useEffect(() => {
    if (!isLoggedIn) return;

    // 监听用户常规操作事件
    const activityEvents = ['mousemove', 'keydown', 'click', 'scroll'];
    activityEvents.forEach(event => {
      document.addEventListener(event, handleUserActivity);
    });

    // 每秒检查一次空闲状态,提升精度
    checkIntervalRef.current = setInterval(() => {
      const currentTime = new Date();
      const idle = currentTime - lastActive;
      setIdleTime(idle);

      // 仅在未弹出提示时触发提示
      if (idle > config.threshold && !open) {
        setOpen(true);
        // 设置登出定时器
        logoutTimeoutRef.current = setTimeout(() => {
          config.action();
          setOpen(false);
        }, config.timeout);
      }
    }, 1000);

    // 清理函数:移除监听、清除定时器
    return () => {
      activityEvents.forEach(event => {
        document.removeEventListener(event, handleUserActivity);
      });
      clearInterval(checkIntervalRef.current);
      clearTimeout(logoutTimeoutRef.current);
    };
  }, [isLoggedIn, lastActive, open, handleUserActivity, config]);

  return { open, idleTime, handleUserActivity: resetIdleState };
};

优化点说明

  1. 用 useRef 管理定时器:避免状态更新导致的定时器引用丢失,确保能准确清除旧定时器。
  2. 监听用户常规操作:添加鼠标移动、按键、点击、滚动等事件,实时重置空闲状态,提升体验。
  3. 优化检查频率:改为每秒检查一次,精准计算空闲时间,同时避免重复创建登出定时器。
  4. 简化状态逻辑:分离resetIdleState函数,统一处理状态重置和定时器清除,逻辑更清晰。
  5. 防止重复弹窗:仅在提示未打开时触发弹窗,避免重复提示。

内容的提问来源于stack exchange,提问作者Joseph

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 07:50:22