🚀来做一个支持 React 的前端埋点 SDK,并把它封装成 npm 包 的形式。整体分 3 部分:
- 核心 SDK(独立的采集 & 上报逻辑)
- React Hook / HOC 支持(自动埋点:路由变化、组件渲染、点击事件)
- npm 包结构(package.json、打包配置)
1. SDK 核心逻辑(src/core.ts
)
ts
// src/core.ts (修改后)
export interface AnalyticsOptions {
serverUrl: string;
appId: string;
userId?: string;
maxQueueSize?: number;
interval?: number;
enableErrorTracking?: boolean; // 是否启用错误监控
}
export class AnalyticsSDK {
private serverUrl: string;
private appId: string;
private userId: string;
private queue: EventData[] = [];
private maxQueueSize: number;
private interval: number;
constructor(options: AnalyticsOptions) {
this.serverUrl = options.serverUrl;
this.appId = options.appId;
this.userId = options.userId || this.getUserId();
this.maxQueueSize = options.maxQueueSize || 10;
this.interval = options.interval || 5000;
this.startFlushTimer();
window.addEventListener("beforeunload", () => this.flush(true));
// 开启错误监控
if (options.enableErrorTracking) {
this.setupErrorTracking();
}
}
private getUserId(): string {
let uid = localStorage.getItem("analytics_uid");
if (!uid) {
uid = "uid_" + Math.random().toString(36).substring(2, 9);
localStorage.setItem("analytics_uid", uid);
}
return uid;
}
public track(eventName: string, properties: Record<string, any> = {}) {
const event: EventData = {
eventName,
properties,
appId: this.appId,
userId: this.userId,
timestamp: Date.now(),
url: window.location.href,
ua: navigator.userAgent,
};
this.queue.push(event);
if (this.queue.length >= this.maxQueueSize) {
this.flush();
}
}
private flush(sync = false) {
if (this.queue.length === 0) return;
const events = this.queue.splice(0, this.queue.length);
if (sync && navigator.sendBeacon) {
navigator.sendBeacon(this.serverUrl, JSON.stringify(events));
} else {
fetch(this.serverUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(events),
}).catch(() => {
this.queue.unshift(...events);
});
}
}
private startFlushTimer() {
setInterval(() => this.flush(), this.interval);
}
/** 绑定全局错误监控 */
private setupErrorTracking() {
// JS运行时错误 & 资源加载错误
window.onerror = (message, source, lineno, colno, error) => {
this.track("error_event", {
type: "js_error",
message,
source,
lineno,
colno,
stack: error?.stack || "",
});
};
// Promise 未处理异常
window.addEventListener("unhandledrejection", (event) => {
this.track("error_event", {
type: "unhandled_promise",
reason: event.reason ? event.reason.toString() : "",
});
});
}
}
2. React Hook 支持(src/react.tsx
)
这里做几个封装:
- usePageView:监听页面切换(适配 React Router)
- useTrackClick:点击埋点 Hook
- withTrackClick:HOC 包裹组件
tsx
// src/react.tsx
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
import { AnalyticsSDK } from "./core";
let sdk: AnalyticsSDK | null = null;
export const initAnalytics = (instance: AnalyticsSDK) => {
sdk = instance;
};
// Hook: 页面访问埋点
export const usePageView = () => {
const location = useLocation();
useEffect(() => {
if (sdk) {
sdk.track("page_view", { path: location.pathname });
}
}, [location]);
};
// Hook: 点击埋点
export const useTrackClick = (eventName: string, props?: Record<string, any>) => {
return () => {
if (sdk) {
sdk.track(eventName, props);
}
};
};
// HOC: 包裹组件自动埋点点击
export const withTrackClick = <P extends object>(
Component: React.ComponentType<P>,
eventName: string,
props?: Record<string, any>
) => {
return (componentProps: P) => {
const handleClick = () => {
if (sdk) {
sdk.track(eventName, props);
}
};
return <div onClick={handleClick}><Component {...componentProps} /></div>;
};
};
3. npm 包目录结构
my-analytics-sdk/
├── package.json
├── tsconfig.json
├── rollup.config.js # 打包配置
├── src/
│ ├── core.ts # SDK核心逻辑
│ ├── react.tsx # React hooks/HOC
│ └── index.ts # 入口文件
src/index.ts
ts
export { AnalyticsSDK } from "./core";
export { initAnalytics, usePageView, useTrackClick, withTrackClick } from "./react";
4. 打包配置(rollup.config.js
)
js
import typescript from "@rollup/plugin-typescript";
import { terser } from "rollup-plugin-terser";
export default {
input: "src/index.ts",
output: [
{ file: "dist/index.cjs.js", format: "cjs" },
{ file: "dist/index.esm.js", format: "esm" },
],
plugins: [typescript(), terser()],
external: ["react", "react-router-dom"],
};
5. 使用示例(React 项目中)
tsx
// App.tsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AnalyticsSDK, initAnalytics, usePageView, useTrackClick } from "my-analytics-sdk";
const sdk = new AnalyticsSDK({
serverUrl: "https://your-server.com/track",
appId: "my-app",
});
initAnalytics(sdk);
function Home() {
usePageView();
const handleClick = useTrackClick("click_home_button", { id: "btn1" });
return <button onClick={handleClick}>点我埋点</button>;
}
function About() {
usePageView();
return <div>About Page</div>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
✅ 至此,就有了一个 支持 React 路由 + 手动/自动埋点的 SDK ,并且能发布成 npm 包 。
只需 npm publish
就能在其他项目中直接 npm install my-analytics-sdk
使用。
6. 增加错误监控
加一个 错误监控(window.onerror + unhandledrejection)自动埋点 的功能,让 SDK 既能做埋点又能做异常监控
错误监控其实是前端埋点 SDK 很重要的一部分。我们可以在 SDK 初始化时 自动监听全局的:
window.onerror
→ 捕获同步 JS 错误 / 资源加载错误window.onunhandledrejection
→ 捕获未处理的 Promise 异常
并把这些错误也当作特殊事件(error_event
)上报。
6.1 使用方式(React 项目里)
tsx
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { AnalyticsSDK, initAnalytics, usePageView } from "my-analytics-sdk";
const sdk = new AnalyticsSDK({
serverUrl: "https://your-server.com/track",
appId: "my-app",
enableErrorTracking: true, // 开启错误监控
});
initAnalytics(sdk);
function Home() {
usePageView();
const throwError = () => {
throw new Error("手动触发一个错误试试");
};
const rejectPromise = () => {
Promise.reject("手动触发 Promise 异常");
};
return (
<div>
<h1>首页</h1>
<button onClick={throwError}>抛 JS 错误</button>
<button onClick={rejectPromise}>抛 Promise 错误</button>
</div>
);
}
function About() {
usePageView();
return <div>关于页面</div>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
6.2 上报到服务端的错误数据示例
json
[
{
"eventName": "error_event",
"properties": {
"type": "js_error",
"message": "Uncaught Error: 手动触发一个错误试试",
"source": "http://localhost:3000/static/js/main.js",
"lineno": 42,
"colno": 13,
"stack": "Error: 手动触发一个错误试试\n at onClick..."
},
"appId": "my-app",
"userId": "uid_xxx",
"timestamp": 1693042000000,
"url": "http://localhost:3000/",
"ua": "Mozilla/5.0 ..."
}
]
✅ 至此,SDK 已经同时支持 埋点 + 异常监控 ,而且能直接作为 npm 包发布。
这样就能在实际项目中做到埋点、错误监控一体化。