一个轻量、易用的 React 埋点 Hooks 库,支持点击埋点、曝光埋点、页面停留时长埋点、组件首次渲染埋点、自定义埋点,内置智能批量上报和增强型失败重试机制,适配 React/Next.js 项目。
- 🚀 开箱即用:提供常用埋点场景的 Hooks,无需重复封装
- 📦 智能批量上报:支持埋点批量入队、定时/定量触发上报,减少网络请求
- 🔄 增强型失败重试:内置 localStorage 缓存 + 指数退避算法,批量/单条自适应重试,确保埋点不丢失
- 🎯 精准控制:曝光埋点支持可见比例、单次触发配置
- ⏱ 精准页面停留时长:自动监听用户活跃、无操作超时、页面显隐,统计真实有效停留时长
- ⚡ 轻量无依赖:体积小,不引入额外冗余依赖
- 📝 完整 TypeScript 类型:提供完善的类型声明,开发更友好
- 🌐 框架适配:兼容 React 16+、Next.js(App Router/Pages Router)
# npm
npm install react-track-hooks --save
# yarn
yarn add react-track-hooks
# pnpm
pnpm add react-track-hooks在 React/Next.js 项目的入口文件(如 App.tsx/layout.tsx)中配置全局参数:
import { useEffect } from "react";
import { setTrackGlobalConfig, useTrackRetryListener, InitBatchTracker, DestroyBatchTracker } from 'react-track-hooks';
const trackConfig = {
trackUrl: '/api/track',
batchTrackUrl: '/api/track/batch',
enable: true,
enableBatch: true,
retryConfig: {
maxRetryTimes: 3,
initialDelay: 1000,
delayMultiplier: 2
},
batchConfig: {
batchSize: 10,
batchInterval: 5000,
},
exposureConfig: {
exposureOnce: true,
exposureThreshold: 0.5,
},
pageStayConfig: {
timeout: 30 * 1000, // 用户不活跃时间
minDuration: 2 * 1000, // 最短有效时间
maxDuration: 2 * 60 * 1000, // 最长活跃时间
checkInterval: 1000, // 检查用户是否活跃计时器
}
};
function App() {
// 启用失败埋点自动重试监听(全局只执行一次)
useTrackRetryListener();
useEffect(() => {
// 全局埋点配置(只执行一次)
setTrackGlobalConfig(trackConfig);
// 启用批量上报定时器以及页面卸载/关闭监听
InitBatchTracker(trackConfig);
return () => {
DestroyBatchTracker()
}
}, []);
return <>{/* 你的应用内容 */}</>;
}// app/components/TrackProvider.tsx (客户端组件)
'use client';
import { useEffect } from "react";
import { setTrackGlobalConfig, useTrackRetryListener, InitBatchTracker, DestroyBatchTracker } from 'react-track-hooks';
const trackConfig = {
trackUrl: '/api/track',
batchTrackUrl: '/api/track/batch',
enable: true,
enableBatch: true,
retryConfig: {
maxRetryTimes: 3,
initialDelay: 1000,
delayMultiplier: 2
},
batchConfig: {
batchSize: 10,
batchInterval: 5000,
},
exposureConfig: {
exposureOnce: true,
exposureThreshold: 0.5,
},
pageStayConfig: {
timeout: 30 * 1000, // 用户不活跃时间
minDuration: 2 * 1000, // 最短有效时间
maxDuration: 2 * 60 * 1000, // 最长活跃时间
checkInterval: 1000, // 检查用户是否活跃计时器
}
};
export const TrackProvider = () => {
// 启用失败埋点自动重试监听(全局只执行一次)
useTrackRetryListener();
useEffect(() => {
// 全局埋点配置(只执行一次)
setTrackGlobalConfig(trackConfig);
// 启用批量上报定时器以及页面卸载/关闭监听
InitBatchTracker(trackConfig);
return () => {
DestroyBatchTracker()
}
}, []);
return null;
};
// app/layout.tsx (根布局)
import { TrackProvider } from './components/TrackProvider';
export default function RootLayout({ children }) {
return (
<html>
<body>
<TrackProvider />
{children}
</body>
</html>
);
}// React 项目/Next.js 客户端组件
'use client';
import { useTrackInit } from 'react-track-hooks';
export const TrackProvider = () => {
// 一键初始化:包含全局配置设置 + 批量上报初始化 + 失败重试监听
useTrackInit({
trackUrl: '/api/track',
batchTrackUrl: '/api/track/batch',
enable: true,
enableBatch: true,
retryConfig: {
maxRetryTimes: 3,
initialDelay: 1000,
delayMultiplier: 2
},
batchConfig: {
batchSize: 10,
batchInterval: 5000
},
pageStayConfig: {
timeout: 30 * 1000,
minDuration: 2 * 1000,
maxDuration: 2 * 60 * 1000,
checkInterval: 1000
}
});
return null;
};import { useTrackClick } from 'react-track-hooks';
function ButtonComponent() {
// 初始化点击埋点
const handleClick = useTrackClick(
'button_click', // 埋点事件名
{ button_type: 'primary', page: 'home' }, // 基础参数
{
enable: true,
enableBatch: false // 单个埋点关闭批量上报(覆盖全局配置)
}
);
return (
// 点击时可追加动态参数
<button onClick={(e) => handleClick(e, { click_pos: 'top' })}>
测试点击埋点
</button>
);
}import { useTrackExposure } from 'react-track-hooks';
function CardComponent() {
// 初始化曝光埋点(返回 ref 绑定到目标元素)
const exposureRef = useTrackExposure<HTMLDivElement>(
'card_exposure', // 埋点事件名
{ card_id: '123456', card_type: 'product' }, // 基础参数
{
exposureThreshold: 0.8, // 元素可见比例≥80%时触发
exposureOnce: true, // 仅触发一次曝光
}
);
return (
<div ref={exposureRef} style={{ width: '300px', height: '200px' }}>
这是一个曝光埋点卡片
</div>
);
}import { useTrackPageStay } from 'react-track-hooks';
function HomePage() {
// 初始化页面停留埋点(组件挂载时自动监听)
useTrackPageStay(
'page_stay', // 埋点事件名
{ page_path: '/home', platform: 'web' }, // 基础参数
);
return <div>首页内容</div>;
}import { useTrackFirstRender } from 'react-track-hooks';
const MyComponent = () => {
// 组件首次渲染时触发埋点
useTrackFirstRender(
'my_component_first_render', // 事件名
{ componentName: 'MyComponent' }, // 自定义参数
{ enableBatch: false } // 配置(比如关闭批量,立即上报)
);
return <div>我的组件</div>;
};import { useTrackCustom } from 'react-track-hooks';
function FormComponent() {
// 初始化自定义埋点
const triggerCustomTrack = useTrackCustom(
'form_submit', // 埋点事件名
{ form_id: 'login_form' }, // 基础参数
);
const handleSubmit = () => {
// 手动触发自定义埋点,可追加动态参数
triggerCustomTrack({ submit_time: Date.now(), status: 'success' });
};
return <button onClick={handleSubmit}>提交表单</button>;
}import { retryFailedTracks } from 'react-track-hooks';
function RetryButton() {
const handleRetry = async () => {
// 手动触发失败埋点重试(force: true 强制立即重试,忽略指数退避时间)
await retryFailedTracks(true);
alert('失败埋点重试流程已执行!');
};
return <button onClick={handleRetry}>重试失败埋点</button>;
}| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| trackUrl | string | 是 | - | 单条埋点上报接口地址 |
| batchTrackUrl | string | 否 | /api/track/batch | 批量埋点上报接口地址 |
| enable | boolean | 否 | true | 是否开启埋点 |
| enableBatch | boolean | 否 | true | 是否开启批量上报 |
| retryConfig | RetryConfig | 否 | 见下方 | 重试配置 |
| batchConfig | BatchConfig | 否 | 见下方 | 批量上报配置 |
| exposureConfig | ExposureConfig | 否 | 见下方 | 曝光配置 |
| pageStayConfig | PageStayConfig | 否 | 见下方 | 页面停留时长配置 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| maxRetryTimes | number | 3 | 最大重试次数(超过则清理埋点) |
| initialDelay | number | 1000 | 初始重试延迟(ms) |
| delayMultiplier | number | 2 | 延迟倍数(指数退避算法) |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| batchSize | number | 10 | 触发批量上报的队列容量上限 |
| batchInterval | number | 5000 | 触发批量上报的时间间隔(ms) |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| exposureOnce | boolean | true | 曝光埋点是否只触发一次 |
| exposureThreshold | number | 0.5 | 元素可见比例(0~1)达到多少触发曝光 |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| timeout | number | 1800000 | 无操作超时时间(ms),超时后暂停计时 |
| minDuration | number | 2000 | 最小有效时长(ms),低于该值不上报 |
| maxDuration | number | 3600000 | 最大单页时长(ms),防止异常超长数据 |
| checkInterval | number | 1000 | 定时检查用户活跃状态的间隔(ms) |
- 作用:一站式初始化埋点系统,整合
setTrackGlobalConfig+ 批量上报初始化 +useTrackRetryListener,简化全局配置流程 - 特性:
- 内置单例校验,确保只初始化一次
- 自动根据
enableBatch初始化/销毁批量上报调度器 - 内部自动调用
useTrackRetryListener启用失败重试监听
- 适用场景:替代手动调用
setTrackGlobalConfig+useTrackRetryListener,简化入口配置代码参数 类型 必填 说明 config TrackGlobalConfig 是 全局埋点配置(同 setTrackGlobalConfig参数)返回值 void - 无返回值
- 作用:全局监听页面状态(初始化/切回标签页/浏览器空闲),自动触发失败埋点重试
- 特性:内置防并发机制,避免重复执行重试流程
- 注意:全局只需调用一次,建议放在项目入口;使用
useTrackInit时无需手动调用
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| eventName | string | 是 | 埋点事件名 |
| baseParams | TrackParams | 否 | 基础业务参数 |
| config | TrackConfig | 否 | 单个埋点配置(可覆盖全局批量/重试配置) |
| 返回值 | (e?, extraParams?) => void | - | 点击事件处理函数,可追加动态参数 |
通用曝光埋点 Hook,返回泛型 ref,可绑定到任意 DOM 元素,元素进入视口时触发埋点上报。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| eventName | string | 是 | 埋点事件名 |
| baseParams | TrackParams | 否 | 基础业务参数,会和曝光自动采集参数合并上报 |
| config | TrackConfig | 否 | 曝光配置 + 批量/重试配置 |
| 泛型 T | T extends HTMLElement | 否 | 可选,指定 ref 绑定的 DOM 元素类型(默认 HTMLElement) |
| 返回值 | React.RefObject | - | 需绑定到目标元素的 ref,类型与泛型 T 一致 |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| eventName | string | 是 | 埋点事件名 |
| baseParams | TrackParams | 否 | 基础业务参数 |
| config | TrackConfig | 否 | 单个埋点配置(可覆盖全局批量/重试配置) |
- 自动监听:页面显隐、用户操作(鼠标/键盘/滚动/触摸)、无操作超时、组件卸载、页面关闭
- 核心逻辑:
- 仅统计用户真实活跃时段(无操作超时/切后台时暂停计时)
- 用户重新活跃时自动恢复计时,累计有效时长
- 有效时长 = 最后活跃时间 - 计时开始时间(自动截断最大时长,过滤最小时长)
- 上报时机:
- 无操作超时 → 暂停计时并走默认上报逻辑(支持批量)
- 组件卸载 → 暂停计时并走默认上报逻辑(支持批量)
- 页面隐藏/关闭 → 暂停计时并绕过批量队列,直接单独上报(保证数据不丢失)
- 上报自动携带字段:
stayTime: 有效时长(ms)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| eventName | string | 是 | 埋点事件名 |
| baseParams | TrackParams | 否 | 基础业务参数(如组件名称、页面标识等固定参数) |
| config | TrackConfig | 否 | 单个埋点配置(可覆盖全局批量/重试/上报地址等配置) |
| 返回值 | void | - | 无返回值,Hook 内部自动触发埋点,无需手动调用 |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| eventName | string | 是 | 埋点事件名 |
| baseParams | TrackParams | 否 | 基础业务参数 |
| config | TrackConfig | 否 | 单个埋点配置(可覆盖全局批量/重试配置) |
| 返回值 | (extraParams?) => void | - | 手动触发埋点的函数 |
增强型失败埋点重试函数,支持批量/单条自适应重试,内置指数退避算法和防并发机制。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| force | boolean | false | 是否强制立即重试(忽略指数退避时间) |
| 返回值 | Promise | - | 重试流程完成的 Promise |
interface TrackParams {
eventName: string;
type: 'click' | 'exposure' | 'page_stay' | 'custom';
[key: string]: any; // 自定义业务参数
}interface TrackConfig extends Partial<TrackGlobalConfig> {
exposureOnce?: boolean; // 曝光埋点仅生效一次(默认 true)
exposureThreshold?: number; // 曝光埋点触发阈值(0-1,默认 0.5)
}export interface TrackGlobalConfig {
// 埋点上报接口 URL
trackUrl: string;
// 批量上报接口 URL
batchTrackUrl?: string;
// 是否开启埋点
enable?: boolean;
// 是否开启批量上报
enableBatch?: boolean
// 重试配置
retryConfig?: {
maxRetryTimes: number;
initialDelay: number;
delayMultiplier: number;
};
// 批量上报配置
batchConfig?: {
batchSize: number, // 队列容量上限
batchInterval: number, // 触发上报间隔
};
exposureConfig?: {
exposureOnce?: boolean; // 暴露是否只触发一次
exposureThreshold: number; // 元素暴露多少部分(0-1)触发
}
pageStayConfig?: {
timeout: number; // 无操作超时时间,超时暂停计时,并进行上报
minDuration: number; // 最小有效时长,低于该值不上报
maxDuration: number; // 最大单页时长,防止异常数据
checkInterval: number; // 活跃检查间隔
};
}- 入队规则:开启批量上报后,埋点参数先进入内存队列,而非直接发送请求
-
触发条件:满足以下任一条件即触发批量上报:
- 队列长度达到
batchSize(默认 10) - 距离上次上报超过
batchInterval(默认 5000ms)
- 队列长度达到
- 异常处理:批量上报失败时,所有埋点会自动转入失败队列,参与重试逻辑
-
优先级:单个埋点配置的
enableBatch优先级高于全局配置 -
警告:全局配置
enableBatch为true可以通过单个埋点的配置进行关闭。但是全局不开启enableBatch并进行初始化,通过单个埋点配置开启的批量上报将无效。
-
可靠监听:基于
visibilitychange监听页面关闭、标签隐藏、浏览器最小化等场景,兼容性覆盖所有现代浏览器及移动端,优于不可靠的beforeunload。 - 紧急批量上报:页面隐藏时立即触发全量批量上报,绕过定时等待,确保队列内埋点不丢失。
-
请求强保障:上报请求携带
keepalive: true,浏览器会保证请求在页面卸载后仍可在后台完成发送。 -
失败兜底重试:页面关闭时的批量上报若因网络异常失败,埋点会自动存入
localStorage失败队列,遵循增强型失败重试机制,在页面重新打开、切回前台时自动重试,确保埋点数据100%不丢失。 - 无重复上报:上报前同步清空队列,页面重新激活后自动重启定时任务,常规批量流程与关闭上报逻辑互不冲突。
- 失败存储:上报失败的埋点会存入 localStorage,避免页面刷新丢失
-
前置清理:重试前自动清理超过
maxRetryTimes的过期埋点,避免内存膨胀 -
智能筛选:基于指数退避算法筛选可重试埋点:
例如:初始延迟 1s,倍数 2 → 第1次重试延迟 1s,第2次 2s,第3次 4s...
重试延迟时间 = initialDelay * (delayMultiplier ^ 当前重试次数) -
自适应重试:
- 开启批量时:调用
batchTrackUrl一次性重试所有符合条件的埋点 - 关闭批量时:逐条调用
trackUrl重试,失败单条不影响其他
- 开启批量时:调用
-
状态更新:
- 重试成功:从失败队列移除对应埋点
- 重试失败:自动更新
retryCount和retryTime,等待下次重试
-
重试时机:
- 首屏渲染 3 秒后自动重试
- 页面从不可见变为可见时重试
- 浏览器空闲时周期性重试(最迟 30 秒一次)
- 埋点上报成功后自动触发重试
- 可通过
retryFailedTracks手动触发
- 内置
isRetryRunning状态标记,避免同时执行多个重试流程 - 所有异常被统一捕获,确保
isRetryRunning能正常重置
- 活跃检测:监听鼠标、键盘、滚动、点击、touch 事件,实时标记用户活跃状态,更新最后活跃时间
-
计时规则:
- 页面可见时自动开始计时,切后台/隐藏时暂停并上报
- 用户无操作超时(
timeout)暂停计时,并进行上报。重新操作时恢复计时 - 有效时长 = 最后活跃时间 - 计时开始时间(仅统计真实活跃时段)
- 自动过滤 <
minDuration的无效时长,截断 >maxDuration的异常时长
-
上报时机与策略:
- 无操作超时/组件卸载:走默认上报逻辑(支持批量),保证常规场景下的性能
-
页面隐藏/关闭:使用
enableBatch: false强制单条上报 +keepalive: true,绕过批量队列确保数据不丢失
-
触发保障:
- 用兼容性更强visibilitychange替代beforeunload: visibilitychange的兼容性更强,beforeunload在safari浏览器不兼容
- 用keepalive:true替代sendBeacon: 保证页面关闭的情况下也能触发失败缓存的回调
- 内置单例校验,避免重复初始化
- 自动执行:
-
setTrackGlobalConfig配置全局参数 -
InitBatchTracker初始化批量上报调度器(仅当enableBatch: true时) -
useTrackRetryListener启用失败重试监听
-
- 组件卸载时自动执行
DestroyBatchTracker清理批量上报定时器
- React 版本:支持 React 16.8+(Hooks 最低兼容版本)
- Next.js 版本:支持 Next.js 13+(App Router/Pages Router)
- 浏览器兼容:支持所有现代浏览器,IE 需自行兼容 Promise/IntersectionObserver/requestIdleCallback
A: 确保安装的是最新版本,若仍报错,可在项目中添加类型声明文件:
// types/react-track-hooks.d.ts
declare module 'react-track-hooks';A: 检查:
- 元素是否绑定 ref;
- 可见比例是否达到
exposureThreshold; - 元素是否为固定定位/脱离文档流(需确保 IntersectionObserver 能检测到);
- 全局/单个埋点的
enable是否为true。
A: 检查:
- 全局/单个埋点的
enableBatch是否为true; -
batchTrackUrl是否配置正确; - 队列长度是否未达到
batchSize且未到batchInterval时间。
A: 确保:
- 已调用
useTrackRetryListener()(使用useTrackInit则自动调用); - 重试次数未超过
maxRetryTimes; - localStorage 未被禁用(失败埋点依赖 localStorage 存储);
- 重试时间未到(可通过
retryFailedTracks(true)强制重试验证)。
A: 批量重试为原子操作:
- 接口返回 2xx → 所有埋点视为成功,从失败队列移除
- 接口返回非 2xx/网络错误 → 所有埋点视为失败,更新重试次数
A: 本 Hook 统计的是有效活跃时长:
- 用户无操作超时 → 暂停计时
- 页面切后台 → 进行上报
- 重新操作 → 恢复计时 并非从打开到关闭的自然时间。
A: 不会。当触发页面隐藏(hidden)时,Hook 会自动计算最后一段有效时长,并立即调用 triggerSingleTrack。通过 fetch 的 keepalive: true 属性,浏览器会将该请求标记为“后台独立任务”,确保即便页面文档对象(Document)被销毁,请求依然能在后台成功发出。
A: 配置 pageStayConfig.timeout,默认 30 分钟。
A:
-
useTrackInit是对setTrackGlobalConfig+InitBatchTracker+useTrackRetryListener的封装,简化初始化代码; - 手动配置需分别调用多个方法,
useTrackInit一键完成; -
useTrackInit内置单例校验和自动清理逻辑,避免重复初始化/内存泄漏。
A: 不会。页面关闭/隐藏时会立即强制执行批量上报,请求自带 keepalive: true 确保浏览器后台发送;若上报失败,数据会自动存入失败队列,下次页面打开时自动重试,完全避免数据丢失。
MIT © liujingmin