背景
在Web应用开发中,检测用户空闲状态是常见的业务需求场景:
- 安全场景:银行/医疗系统在用户无操作15分钟后自动锁定界面
- 资源优化:视频网站暂停播放器控制栏的强制显示以节约计算资源
- 体验增强:在线文档工具在用户停止输入后自动保存草稿
- 精准推荐:电商平台根据用户停留时长优化推荐策略
传统实现需要手动绑定mousemove/keydown等事件,并通过定时器管理状态,而react-use提供的useIdle Hook将这些逻辑封装为可复用的解决方案。
设计思路
该Hook的核心思路通过四个步骤实现智能检测:
- 事件监听:默认监听17种用户交互事件(点击、滚动、触摸等)
- 定时轮询:通过requestAnimationFrame实现高性能时间检测
- 状态切换:在活动事件触发时重置空闲计时
- 自动清理:组件卸载时自动移除事件监听避免内存泄漏
功能简介
ini
const isIdle = useIdle(initialState?, options?);
-
参数:
- initialState:初始空闲状态(默认false)
- options:配置对象包含超时时间、检测间隔等
-
返回:
- isIdle:当前是否处于空闲状态
- reset:手动重置空闲计时的方法
API详解
配置参数(Options)
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
timeout | number | 60_000 | 判定空闲的毫秒阈值(推荐业务设置为5-15分钟) |
throttle | number | 100 | 检测间隔的节流时间,平衡精度与性能 |
events | string[] | [预设17种] | 需要监听的活动事件类型(可根据业务扩展) |
initialState | boolean | false | 初始化时是否立即视为空闲 |
autoRun | boolean | true | 是否自动开始检测(特殊场景可关闭) |
lead | boolean | true | 是否在初始化时立即执行首次检测 |
返回值
属性 | 类型 | 说明 |
---|---|---|
isIdle | boolean | 当前是否处于空闲状态 |
reset | () => void | 强制重置空闲计时的方法(如弹窗后重新计时) |
实现原理
核心代码结构
ini
function useIdle(ms: number = 60e3, initialState = false) {
const [state, setState] = useState(initialState);
const timer = useRef<number>();
const mounted = useRef(true);
const onEvent = useCallback(() => {
if (!state) {
resetTimer();
}
setState(false);
}, [state]);
const resetTimer = () => {
clearTimeout(timer.current);
timer.current = window.setTimeout(() => {
if (mounted.current) setState(true);
}, ms);
};
useEffect(() => {
const events = ['mousemove', 'keydown', ...];
events.forEach(e => window.addEventListener(e, onEvent));
resetTimer();
return () => {
mounted.current = false;
events.forEach(e => window.removeEventListener(e, onEvent));
};
}, [onEvent]);
return state;
}
关键实现点
- 性能优化:采用requestAnimationFrame替代常规定时器
- 事件覆盖:默认监听包括触摸事件在内的移动端操作
- 状态安全:通过mounted标记避免组件卸载后的状态更新
- 内存管理:自动清理事件监听和定时器
业务场景Demo
案例一:安全锁屏机制
scss
function SecurityLock() {
const isIdle = useIdle(15 * 60 * 1000); // 15分钟
const navigate = useNavigate();
useEffect(() => {
if (isIdle) {
showModal('您已长时间未操作,即将退出登录');
logout();
navigate('/login');
}
}, [isIdle]);
return <MainApp />;
}
案例二:视频播放器控制栏优化
ini
function VideoPlayer() {
const [showControls, setControls] = useState(true);
const isIdle = useIdle(3000, { events: ['mousemove'] });
useLayoutEffect(() => {
setControls(!isIdle);
}, [isIdle]);
return (
<div className="player">
<video src="..." />
{showControls && <ControlBar />}
</div>
);
}
案例三:表单草稿自动保存
scss
function OrderForm() {
const [formData, setData] = useState();
const isIdle = useIdle(5 * 60 * 1000, {
events: ['mousedown', 'keypress', 'scroll']
});
useEffect(() => {
if (isIdle && formData) {
autoSaveDraft(formData);
}
}, [isIdle]);
return <Form fields={...} />;
}
总结与建议
优势对比
方案 | 代码量 | 维护成本 | 跨设备支持 | 性能影响 |
---|---|---|---|---|
原生实现 | 高 | 高 | 需要适配 | 中 |
useIdle | 低 | 低 | 自动覆盖 | 优化 |
最佳实践
- 超时设置:金融类建议10-15分钟,内容类可放宽至30分钟
- 事件选择:移动端应用需保留touch相关事件监听
- 状态组合:推荐与useLocalStorage结合实现草稿持久化
- 异常处理:在SSR场景下需要做服务端兼容
扩展建议
对于复杂场景可考虑以下增强:
ini
// 多阶段空闲检测
const [phase, setPhase] = useState(0);
useIdle(300_000, {
onChange: idle => setPhase(p => idle ? Math.min(p+1,3) : 0)
});
// 与页面可见性结合
const isVisible = usePageVisibility();
const trueIdle = isIdle && !isVisible;
通过合理使用useIdle,开发者可以快速实现符合业务需求的用户行为感知系统,在安全性与用户体验之间找到最佳平衡点。