react-use 的 useIdle Hook 详解:基于交互事件实时追踪用户空闲状态
【免费下载链接】react-useReact Hooks — 👍项目地址: https://gitcode.com/gh_mirrors/re/react-use
导读
useIdle是 react-use(package.json 中版本为 17.6.1)提供的一个sensor(传感器)类 Hook,用于追踪当前页面上的用户是否处于空闲(idle)状态。本文以 docs/useIdle.md 为骨架,结合 src/useIdle.ts 源码与 stories/useIdle.story.tsx 演示,完整讲解其用法、参数语义与底层实现原理,帮助你在此基础上实现自动暂停、节电模式、安全锁定、在线状态提示等实战功能。
useIdle 是什么
useIdle在官方 README.md 中被描述为 "tracks whether user is being inactive",即通过监听用户的交互事件来判断其是否处于空闲状态。它归属于 react-use 的 Sensors 分类(对应 docs/Sensors.md 文档体系),与useMouse、useScroll、useKeyPress等 Hook 一样,将浏览器事件抽象为可响应的 React 状态。
Hook 的核心能力是:
- 用户在一段时间内没有任何交互(鼠标、键盘、触摸、滚动、窗口变化等)时,返回值切换为
true; - 一旦用户恢复交互,返回值立即切回
false,并重新开始计时; - 支持自定义空闲判定时长与初始状态。
快速上手
根据 docs/useIdle.md 的 Usage 示例,最简单的用法如下:
import {useIdle} from 'react-use'; const Demo = () => { const isIdle = useIdle(3e3); return ( <div> <div>User is idle: {isIdle ? 'Yes 😴' : 'Nope'}</div> </div> ); };useIdle会从 src/index.ts 作为具名导出对外暴露(对应源码第 35 行export { default as useIdle } from './useIdle';),因此直接import { useIdle } from 'react-use'即可使用。
一个可调节延迟的交互式演示
仓库的 stories/useIdle.story.tsx 提供了一个更贴近实战的 Storybook 演示:用useState保存延迟毫秒数,通过<input type="number">实时修改并传入useIdle,界面同步显示 "User is idle: Yes / No"。这验证了useIdle对ms参数是响应式的——延迟变化后判定逻辑会随之更新:
import { storiesOf } from '@storybook/react'; import * as React from 'react'; import { useIdle } from '../src'; const Demo = () => { const [idleDelay, setIdleDelay] = React.useState(3e3); const isIdle = useIdle(idleDelay); return ( <div> Idle delay ms:{' '} <input type="number" value={idleDelay} onChange={({ target }) => setIdleDelay(+target.value)} /> <div>User is idle: {isIdle ? 'Yes' : 'No'}</div> </div> ); }; storiesOf('Sensors/useIdle', module) .add('Docs', () => <ShowDocs md={require('../docs/useIdle.md')} />) .add('Demo', () => <Demo />);API 参考与参数语义
依据 docs/useIdle.md 的 Reference 章节:
useIdle(ms, initialState);| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ms | number | 60e3(一分钟) | 用户空闲多少毫秒后判定为 idle |
initialState | boolean | false | 初始是否把用户视为空闲,默认认为用户一开始是活跃的 |
对照 src/useIdle.ts 第 8~12 行的真实签名:
const useIdle = ( ms: number = oneMinute, // oneMinute = 60e3 initialState: boolean = false, events: string[] = defaultEvents ): boolean => {可以看到文档中的两个参数与源码完全一致,返回值是一个boolean:
true表示用户当前被判定为空闲;false表示用户处于活跃状态。
源码中隐藏的第三个参数:events
值得注意的是,源码比文档多暴露了一个未在文档中记录的第三参数events(默认值为defaultEvents,见源码第 5 行):
const defaultEvents = ['mousemove', 'mousedown', 'resize', 'keydown', 'touchstart', 'wheel'];也就是说,useIdle默认监听以下六类窗口事件来判定用户活跃:
mousemove/mousedown:鼠标移动与点击;resize:窗口尺寸变化;keydown:键盘按键;touchstart:触摸屏触摸;wheel:滚轮滚动。
如果你希望自定义"什么算活跃",可以传入第三参数,例如只把键盘与鼠标点击视为活跃交互:
const isIdle = useIdle(5e3, false, ['keydown', 'mousedown']);底层实现原理剖析
完整源码位于 src/useIdle.ts,其实现可拆解为四条核心机制。
1. 状态管理:useState + mounted 保护
Hook 首先用useState(initialState)保存空闲状态(源码第 13 行),随后在useEffect内部维护一个mounted布尔值与localState缓存:
let mounted = true; let timeout: any; let localState: boolean = state; const set = (newState: boolean) => { if (mounted) { localState = newState; setState(newState); } };set是唯一的状态写入入口:只有在组件仍然挂载时才会更新 React 状态,从机制上避免了组件卸载后的 setState 警告。
2. 事件节流与计时重置:50ms 节流 + setTimeout
用户交互会被统一交给onEvent处理,而onEvent用throttle-debounce包(见 package.json 依赖)的throttle(50, ...)做了50ms 节流,防止高频事件(如 mousemove、scroll)频繁触发重排:
const onEvent = throttle(50, () => { if (localState) { set(false); // 有交互 → 立即从 idle 恢复为活跃 } clearTimeout(timeout); // 取消上一次计时 timeout = setTimeout(() => set(true), ms); // 重新计时 ms 毫秒 });这段逻辑揭示了判定的完整闭环:
- 任意监听到的交互发生,若当前是 idle(
localState === true),立即置回活跃; - 清除旧计时器,重新启动一个
ms毫秒的计时器; - 计时器到期触发
set(true),用户进入空闲状态。
3. 页面可见性兜底:visibilitychange
仅靠窗口事件存在一个盲区:用户切换标签页或最小化窗口后,可能长时间没有交互事件产生,此时setTimeout会照常到期并置为 idle——这通常符合预期。但若用户切回页面时,源码会通过visibilitychange立即"唤醒"判定:
const onVisibility = () => { if (!document.hidden) { onEvent(); // 页面重新可见 → 视为一次交互,重置空闲计时 } };这个visibilitychange监听绑定在document上(源码第 43 行),与绑定在window上的六个交互事件(源码第 40~42 行,经由 src/misc/util.ts 的on工具函数完成addEventListener)共同构成完整的监听体系。
4. 初始化与清理
useEffect挂载时立即启动第一个计时器(源码第 45 行timeout = setTimeout(() => set(true), ms);),这意味着只要用户在ms毫秒内无任何交互,Hook 就会触发 idle 状态,无需等待第一次事件。
清理函数(源码第 47~54 行)会:
- 将
mounted置为false,阻止后续 setState; - 通过 src/misc/util.ts 的
off工具函数,对events列表逐一执行removeEventListener; - 解绑
document上的visibilitychange。
此外,useEffect的依赖数组为[ms, events](源码第 55 行),当这两个参数变化时会整体重建监听与计时。
5. 从依赖看实现约束
throttle-debounce是 package.json 中声明的直接依赖,throttle(50, ...)的节流窗口为 50ms;- Hook 依赖 DOM API(
window、document),属于浏览器环境专用 Hook。从 src/misc/util.ts 的on/off实现可见,若目标对象不存在(如 SSR 场景下window为undefined),会安全跳过监听而不抛错。
实战场景与注意事项
基于上述机制,useIdle可用于以下典型场景:
- 自动暂停播放:视频或轮播图在用户离开后自动暂停,配合
isIdle切换播放状态; - 会话安全锁定:后台管理系统在用户闲置 N 分钟后锁定屏幕或提示重新登录;
- 节电与资源释放:空闲时停止轮询、动画或实时数据推送,恢复交互后再继续;
- 在线状态提示:类似聊天软件中"离开/在线"状态的自动切换。
使用时注意以下几点:
ms的最小实践值:由于交互事件本身有 50ms 节流,且判定依赖setTimeout,ms过小(如小于 100ms)会导致状态在活跃/空闲之间频繁抖动,建议按业务语义设置(如 3s~5min);- 自定义
events会替换默认六种事件,传入第三参数时请自行覆盖所需的全部交互类型; - 切换标签页行为:页面隐藏期间计时照常推进,切回页面时会被
visibilitychange视为一次交互并重置计时,若业务需要"离开页面即视为空闲",需结合document.hidden自行判断; initialState默认false:组件首次渲染即认为用户活跃,若希望首帧即显示空闲(如静默加载场景),可传入true。
相关资源
- 官方文档:docs/useIdle.md
- 核心实现:src/useIdle.ts
- 事件工具函数:src/misc/util.ts
- 具名导出:src/index.ts
- Storybook 交互演示:stories/useIdle.story.tsx
- 项目总览:README.md
【免费下载链接】react-useReact Hooks — 👍项目地址: https://gitcode.com/gh_mirrors/re/react-use
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考