Skip to content

React 接入(@tripo3d/lens/react) ​

在 React 里做埋点,最直接的方式是把坐标、事件、参数写成元素上的 data-trace* 字符串属性——核心的 行为埋点 · autoTrack 会在点击 / 曝光触发那一刻用 getAttribute 读取它们。这套属性是纯 DOM 契约,JSX 里用 {} 表达式就能写入,props / state 会随重渲染自动反映到属性上,不需要任何适配器。

@tripo3d/lens/react 是这套契约之上的可选 DX 层:把「多个 data-trace* 属性映射 + params 序列化 + 循环引用兜底」打包成 useTraceProps(主原语)与 <Trace>(组件糖),并经 <TraceProvider> 补上命令式逃生口 useTrace()。本文先讲不装适配器的原生写法,再讲初始化,最后才是适配器带来的便利。

先看用法:原生 JSX 写 data-trace* ​

声明式采集只认 DOM 上的 data-trace* 字符串属性——点击 / 曝光触发时才用 getAttribute 读取,SDK 本身不理解、也不需要理解 React 状态。所以你完全可以不装适配器,直接在 JSX 里写属性:坐标固定就用字符串字面量,需要动态就用 {} 表达式。

tsx
import { TraceEvent } from '@tripo3d/lens'; // 事件枚举:避免硬编码 'click' / 'expose'

function Demo({ sku, tab }: { sku: string; tab: string }) {
  return (
    <>
      {/* 1. 纯静态:坐标固定,直接写字符串字面量 */}
      <button data-trace-key="home.banner.buy" data-trace-type="click">
        购买
      </button>

      {/* 2. 动态 params + 枚举多事件:用 {} 表达式,对象须自己 JSON.stringify */}
      <button
        data-trace-key="home.banner.buy"
        data-trace-type={[TraceEvent.Click, TraceEvent.Expose].join(' ')}
        data-trace-params={JSON.stringify({ sku })}
      >
        购买
      </button>

      {/* 3. 坐标拼接:祖先 data-trace 段 + 叶子 data-trace-key,动态段用 {} → home.nav.docs */}
      <section data-trace="home">
        <a data-trace="nav" data-trace-key={tab} data-trace-type="click">
          文档
        </a>
      </section>
    </>
  );
}

为什么 {} 能拿到动态值

React 的更新模型:state / props 变化触发组件重渲染,JSX 重新求值(读到当前快照值),再由 ReactDOM 在 commit 阶段 patch 到 DOM 属性;采集器在触发那一刻读属性,读到的始终是最新值。与 Vue 的区别只在内部机制(React 是整组件重渲染 + diff,而非细粒度依赖追踪),对采集端完全等价。这次没返回的 data-trace* 会随新 props 被 React diff 自动移除,无需手动清理。

要点(规则与手写 HTML 完全一致,详见 行为埋点 · 声明式埋点):

  • 锚点:元素必须带 data-trace 或 data-trace-key 才会被采集;单独写 data-trace-type / data-trace-params 不会触发。拼出的坐标须为合法 TracePath(2–8 段),否则静默丢弃。
  • 事件:data-trace-type 缺省为 click;多事件用空格分隔("click expose")。token 可从 @tripo3d/lens 引入 TraceEvent 常量避免硬编码——data-trace-type={TraceEvent.Click},多事件 data-trace-type={[TraceEvent.Click, TraceEvent.Expose].join(' ')}。但请注意:JSX 里 data-* 属性的值类型是 string,享受不到 useTraceProps 的 on 字段那样的类型约束(拼错不报错),想要类型安全请用 useTraceProps / <Trace>。
  • 参数:data-trace-params 是三者中唯一需要序列化的——必须传字符串,直接传对象会被写成 "[object Object]" 导致解析失败(见下方警告)。坐标(data-trace / data-trace-key)与事件(data-trace-type)都是字符串,直接写即可。

data-trace-params 不要直接传对象

JSX 里 data-trace-params=(外层 {} 是表达式、内层 { sku } 是对象字面量)会被 React 写成字符串 "[object Object]"(DOM 属性只能存字符串),采集端 JSON.parse 失败、参数丢失。必须写成 data-trace-params={JSON.stringify({ sku })}。另外手写 JSON.stringify 遇到循环引用会直接抛错、可能中断渲染,需自行 try/catch——useTraceProps / <Trace> 已内置这层兜底。

属性写了不等于会上报

上面的写法只是把 data-trace* 落到了 DOM。真正的点击委托与曝光登记,需要一个已初始化、且开启 autoTrack 的核心实例——见下一节。

让埋点生效:初始化核心实例 ​

data-trace* 属性要被采集上报,前提是页面里已经 new TripoLens 并 init(),且装了 TrackingPlugin({ autoTrack: true })。这一步与「用不用适配器」无关,是所有方案的共同前提,只依赖核心包 @tripo3d/lens。

bash
pnpm add @tripo3d/lens
bash
npm install @tripo3d/lens
bash
yarn add @tripo3d/lens

new TripoLens() 出来就是个普通对象,与框架无关。用一个模块导出存起来,声明式采集即可全局生效,命令式 API 也随处可 import:

ts
// lib/trace.ts
import { TripoLens, TrackingPlugin } from '@tripo3d/lens';

// 尽早构造(快照入口 URL);autoTrack 必开,声明式采集才会启动
export const trace = new TripoLens({
  app: 'shop',
  configUrl: 'https://cdn.example.com/lens/config.json',
  endpoint: 'https://collect.example.com/report',
  plugins: [new TrackingPlugin({ autoTrack: true })],
});

await trace.init();

在入口 import 一次这个模块,确保它在应用挂载前执行:

tsx
// main.tsx
import { createRoot } from 'react-dom/client';
import App from './App';
import './lib/trace'; // 触发构造 + init;纯声明式项目到这里就够了

createRoot(document.getElementById('root')!).render(<App />);

适配器不托管实例

与 Nuxt 适配器(模式 A 可自动创建客户端实例)不同,React 适配器只提供原语与 Provider、不构造也不 init 实例。渠道归因要求实例在业务任何 URL 操作之前构造,所以你需自己尽早 new TripoLens(快照入口 URL)——放在 lib/trace.ts 顶层并尽早 import 即可。

构造实例的必填参数 ​

new TripoLens(params) 在构造期即校验 app / configUrl / endpoint 三项,缺任一直接抛错(无内置默认值);其余如 plugins、beforeEmit、onError、fetch 等均为可选,行为/错误/性能采集为 opt-in、需经 plugins 追加对应插件。各参数的类型、归一化与校验规则见 构造参数,初始化时序见 初始化。

埋点依赖 autoTrack

无论手写 data-trace* 还是用原语,真正的点击委托与曝光登记都由 TrackingPlugin({ autoTrack: true }) 内的声明式采集器完成。不开 autoTrack,属性会照写但无人采集。

可选:React 适配器与声明式原语 ​

到这里,纯手写 data-trace* 已经能覆盖全部采集能力——响应式、SSR、曝光时序都不缺。适配器要解决的只是手写的重复与易错:多属性映射、params 的 JSON.stringify、循环引用兜底。装上它,useTraceProps(主原语)与 <Trace>(组件糖)替代逐属性 spread;再经 <TraceProvider> 补上命令式逃生口 useTrace()。

React 适配层已折入 core(子路径 @tripo3d/lens/react),无需额外安装。core(@tripo3d/lens)把 react(^17 || ^18 || ^19)声明为 optional peerDependency,请确保工程内已安装。用 <TraceProvider> 包裹应用根,把上一节构造的实例传入:

tsx
// main.tsx
import { createRoot } from 'react-dom/client';
import { TraceProvider } from '@tripo3d/lens/react';
import App from './App';
import { trace } from './lib/trace'; // 上一节构造好的实例

createRoot(document.getElementById('root')!).render(
  <TraceProvider trace={trace}>
    <App />
  </TraceProvider>,
);

用原语声明式埋点 ​

两种写法,绑定字段与 Vue 一致(key / path / on / params / exposePolicy):

tsx
import { useTraceProps, Trace, TraceEvent } from '@tripo3d/lens/react';

function Demo({ sku, tab }: { sku: string; tab: string }) {
  return (
    <Trace as="section" path="home">
      {/* 主原语:spread 到任意元素,重渲染自动反映 params 变化 */}
      <button 
      {
        ...useTraceProps(
          { 
            key: 'buy', 
            on: 'click', 
            params: { sku } 
          })
        }
      >
        购买
      </button>

      {/* 组件糖:as 指定元素;叶子末段用 traceKey(key 是 React 保留 prop) */}
      <Trace 
        as="button" 
        traceKey="card" 
        on={
          [TraceEvent.Click, TraceEvent.Expose]
        } 
        params={
          { sku }
        }
      >
        卡片
      </Trace>

      {/* 独立扁平 path */}
      <Trace 
        as="a" 
        path={`home.nav.${tab}`} 
        on={
          [TraceEvent.Click, TraceEvent.Expose]
        }
      >
        文档
      </Trace>
    </Trace>
  );
}

与 Vue 的两处差异

  • 叶子末段:useTraceProps 用 key;但 <Trace> 因 React 保留 key prop,改用 **traceKey**。
  • 属性清理:无需 Vue 指令那套 removeAttribute——React 重渲染丢弃不再返回的 props,旧 data-trace* 自然移除。

useTraceProps(binding) 的绑定字段(key / path / on / params / exposePolicy)与 Vue 接入 同。 <Trace> 组件属性:

prop映射说明
as—渲染的元素 / 组件,默认 'div'
traceKeydata-trace-key叶子末段(避开 React 保留的 key)
pathdata-trace扁平坐标或祖先段
ondata-trace-type触发事件,缺省 click;可数组多事件
paramsdata-trace-params上报参数
exposePolicydata-trace-expose-policy元素级曝光去重策略,用 ExposePolicy 枚举取值(如 ExposePolicy.OncePerElement 列表项各报一次)

其余透传 prop 照常工作,且 data-trace* 后铺,不会被业务 prop 覆盖。 坐标规则详见 行为埋点 · 声明式埋点。ExposePolicy 枚举与 TraceEvent 一样由 @tripo3d/lens/react re-export(也可从 @tripo3d/lens 导入)。

手写 vs 原语:该用哪种 ​

场景推荐
坐标固定、无动态参数直接写静态 data-trace*,最轻
有动态 params / 动态坐标段useTraceProps / <Trace>(自动 JSON.stringify + 循环引用兜底 + 多属性映射);或手写 {} + JSON.stringify
想少写重复、避免 [object Object] 坑useTraceProps / <Trace>

简言之:手写 data-trace*(含 {} 表达式)在能力上等价于 useTraceProps / <Trace>,响应式、SSR、曝光时序都不缺;区别只在 DX——原语把「多属性映射 + params 序列化 + 循环引用兜底」打包掉,动态参数场景更省心、更不易错。

纯 HTML / 原生 JS 场景

不在 React 里(服务端模板、静态站、原生 JS)也能用同一套 data-trace*:直接写属性、用 setAttribute / dataset 动态更新即可,用法见 行为埋点 · 声明式埋点。

命令式埋点 ​

命令式 API(track / identify / captureException …)都挂在 TripoLens 实例上, 所以分两步:先拿到实例,再调用。

拿到实例的两种方式 ​

按项目形态选其一。

路径一:自己管实例(最简,CSR 首选)

上一节的 lib/trace.ts 导出的实例与框架无关,哪儿都能 import——纯函数、组件、工具库都行:

ts
// 任意文件直接调用,无需 Hook、无需 Provider
import { trace } from '@/lib/trace';

// 上报标记为 track 类型
trace.track('pay.done', { orderId });
// 需要其他上报类型
trace.click('pay.button');
trace.expo('pay.button');

这条路完全不经过适配器:既不用 <TraceProvider>,声明式 useTraceProps / <Trace> 也照常工作(只写 data-trace* 属性、采集靠核心 autoTrack,本就不需要实例)。 换句话说——手动初始化后实例就已经全局可用,用户自管即可。

路径二:交给 Provider 注入(要逃生口 / DI / SSR 时)

用 <TraceProvider> 包裹应用根传入实例(接入示例见上文 可选:React 适配器与声明式原语), 组件内用 useTrace() 拿:

tsx
<TraceProvider trace={trace}>
  <App />
</TraceProvider>

仅在以下情况才需要走这条路:

需求为什么要把实例传给 Provider
框架风格逃生口不想在组件里硬 import 模块变量,改用 useTrace() 从 Context 拿

为什么 React 是 Provider,而 Vue 是 app.use

两者对等,都只做一件事——把实例放进框架的依赖注入体系,好让组件内的逃生口拿到。 区别只在 API 形态:Vue 有 app.use(plugin, options) 全局插件机制,根注入一次即可; React 没有等价的全局机制,跨层级共享实例的标准做法是 Context,所以用 <TraceProvider> 包裹应用根——子树内 useTrace() 都能读到(读不到则抛错)。 Provider 还顺带注入 onError(params 序列化失败回调),声明式原语也从同一 Context 读取它。

Provider 并非采集前提

Provider 只是命令式逃生口与 onError 的接线点。 声明式属性的采集由核心 autoTrack 完成,挂不挂 Provider 都不影响——纯声明式项目可完全不用 <TraceProvider>(走上面的路径一即可)。

调用命令式 API ​

走路径二注入后,组件内用 useTrace() 拿到实例上报:

tsx
import { useTrace } from '@tripo3d/lens/react';

function CheckoutButton() {
  const trace = useTrace(); // 未挂 Provider 或未传 trace 时抛错
  return (
    <button
      onClick={
        () => trace.track('checkout.submit', { amount: 99 })
      }
    >
      结算
    </button>
  );
}

useTrace() 返回完整 TripoLens 实例

拿到的不只是 track——可调用任意命令式 API: identify / clearUser(见 身份与会话)、 trackImpression / setExposurePolicy(曝光进阶,见 行为埋点 · 进阶)、 captureException / captureMessage(见 错误收集)、 startTransaction / markHydration(见 性能收集)、 setGlobalParams、updateConsent(见 Consent 授权)等。

命令式方法的语义见 行为埋点 · 命令式埋点。

SSR ​

data-trace* 是普通 props,React 服务端渲染即写进 HTML,hydration 后即可被采集,无需额外接线。服务端采集(性能 / 错误)机制见 SSR 采集。

Provider 选项 ​

<TraceProvider>:

prop类型默认说明
traceTripoLens?—已构造(建议已 init)的实例;提供后才有 useTrace()
onError(err) => void—params 序列化失败回调(坐标仍保留,仅丢 params)

故障排查 ​

症状原因处理
埋点元素不上报,控制台无报错没开 autoTrack,属性写了但无人采集构造时加 new TrackingPlugin({ autoTrack: true })
useTrace() 抛错未挂 <TraceProvider>,或未传 trace用 <TraceProvider trace={...}> 包裹应用根
<Trace> 的 key 不生效key 是 React 保留 prop,读不到组件内叶子末段改用 traceKey
点击有上报、曝光没有曝光阈值 / 去重策略见 曝光去重策略 与 AutoTrackOptions.exposeThreshold

下一步 ​

基于 MIT 协议发布(内部使用)