可视化埋点在 React Native 中的实践
1. 背景
笔者所在团队为 Shopee 的本地生活前端团队,用户可以在我们的平台购买优惠券,然后去线下门店使用。随着用户规模不断增加,研究用户行为数据可以更好地指导产品功能设计,提供更加优秀的用户体验。用户行为数据的研究首先涉及到如何采集,即我们常说的“埋点”。
一直以来,我们项目中的埋点都采用代码埋点,每次新增埋点往往是一些重复性的工作,且需要重新发布代码才能生效,为此我们的开发人员叫苦不迭。为了实现在不修改代码的前提下新增埋点,我们调研了可视化埋点和无埋点两种方式。其中,无埋点(又称全埋点)会收集用户在应用里的所有行为,并上报所有相关的数据,由此产生大量无用数据,于是被我们排除了。
而可视化埋点的方式为:通过埋点平台圈选所需埋点的页面元素,进行埋点上报属性的配置与发布,由采集 SDK 同步埋点配置,并根据配置自动进行用户行为数据的采集和发送。正好可以解决我们的问题,因此我们决定采用可视化埋点方案。
在开始介绍我们的系统前,先来看看在 Web 上进行可视化埋点的基本思路:以点击事件为例(下文如果没有特殊说明,均以点击事件为例),Web 可视化埋点一般会提供一个 SDK,SDK 会在 上面监听 事件,借助于事件委托的特性,可以捕获到页面上任意元素的 事件及元素的信息。同时 Web 可视化埋点会提供一个平台,该平台通过 嵌入需要进行埋点配置的网页,然后通过 来进行平台与目标页面的通信。

由于我们的前端技术栈是 React Native,很多地方实现起来都比较有难度,比如无法通过 嵌入页面及 实现平台与目标页面的通信,无法借助事件委托的特性来实现我们的 SDK 等。那么,最后究竟是如何实现的呢?下文将详细展开介绍。
2. 系统介绍
下面按照使用流程来介绍我们的系统。首先,需要在 React Native 客户端接入我们的 SDK。
2.1 客户端接入 SDK
如下所示,我们通过执行 SDK 的 方法导出了 ,该对象又导出了跟点击相关的一些组件供业务方使用,我们直接使用导出的这些点击相关的组件,并指定 即可(关于 后文会做介绍):
import { initGoblin } from '@dp/goblin-sdk-react-native'
export const { TouchableComponent } = initGoblin({ ... })
const {
GButton,
GTouchableHighlight,
GTouchableNativeFeedback,
GTouchableOpacity,
GTouchableWithoutFeedback
} = TouchableComponent
Click Me
这些导出的组件都是利用高阶组件的思想对原来的组件进行了重写,并加入了埋点相关的逻辑。
2.2 连接客户端与可视化埋点平台
接入完 SDK 后,接下来就可以对埋点进行配置了。进行埋点配置前,首先要将我们的 React Native 客户端跟可视化埋点平台连接起来。

如上图所示,埋点配置人员首先需要在可视化埋点平台开始一个埋点任务,可视化埋点平台前端会通过 连接到服务端,服务端会生成一个 发送给前端:

并且会将连接到服务端的 客户端进行登记:
{
25089: {
creator: adminWSClient
}
}
此时埋点配置人员在 React Native 客户端通过 SDK 提供的工具进入连接页面,输入 后通过 连接到埋点可视化平台服务端:

服务端也会将连接到的 客户端进行登记:
{
25089: {
creator: adminWSClient,
connector: rnWSClient
}
}
这样,通过可视化埋点平台服务端,就可以将 React Native 客户端同可视化埋点平台前端间接地连接起来了。此时,可视化埋点服务端会通知前端和 React Native 客户端连接成功。得到消息后,前端会进入配置页面,React Native 客户端则进入配置模式。之后每当配置人员在 React Native 客户端对页面元素进行圈选时,SDK 都会将相关数据发送到可视化埋点平台前端,供配置人员进行配置。
2.3 埋点配置
以下是连接成功后 React Native 客户端及可视化埋点前端对应的效果:

如图所示,当埋点配置人员在 React Native 客户端点击选择所需要埋点的元素时,SDK 会高亮该元素。同时,SDK 还会将当前所选元素的 及埋点属性数据来源集合发送到平台服务端,其中埋点属性数据来源集合由元素对应的 React 组件本身和其祖先组件的 和 属性所组成。
此时埋点配置人员可在平台上新增需要上报的字段并指定字段名、字段值来源,比如图中新增了一个名为 的字段,并指定其值来自于 这个组件 下的 属性。
上文所说的 是当前所选择元素的唯一标识,类似于 Web 中页面元素的 id 或 XPath。其中 id 的优势是比较准确,不会因为页面结构变化而失效,缺点是需要开发人员事先设定,而 XPath 的优势是可以自动生成,但是对页面结构变化比较敏感。
我们知道,每个 React 应用背后其实都对应着一颗由 节点组成的树,而 React 类组件中可以通过 (16 版本)得到当前组件所对应的 节点:

通过从当前组件的 出发一直往上遍历到根部,可以得到一条类似于 XPath 的路径作为该组件的 。但是在实施的时候发现相同的代码在 Android 和 iOS 两个平台生成的 并不一样,这也就意味着如果采取这种方案的话,埋点配置时需要针对两个平台分别配置,这显然会大大增加工作量。所以最后我们不得已放弃了该方案,暂时采用了开发手动给组件设置 的方案。
在遍历的同时,我们还可以可以得到所有祖先组件的 上的 和 ,它们分别对应组件的 和 ,这样我们就可以得到组件的埋点属性数据来源集合了,类似于下图所示:

埋点配置完成后,会发布成 JSON 格式的文件,比如上文的例子发布后会如下所示:
{
...
"item-button": {
"constant": {
"operation": "click"
},
"variable": {
"title": "props.Item.title"
}
}
...
}
每一个配置都是以 为 key 的一个对象,其中对象中 属性表示需要上报的字段的值是固定的,例如 为 表示当前用户的操作为点击, 则表示需要上报的字段的值是动态的,其值是一条取值路径,这里表示 这个字段的值需要从 组件的 中的 属性来获取。
然而在实际使用时又遇到了一个问题:我们的代码在生产环境中打包以后,组件的名称都被混淆了,导致配置人员进行配置的时候根本无法识别。
为了解决这个问题,我们参考 babel-plugin-add-react-displayname 库编写了一个 babel 插件,在打包的时候自动给组件添加 ,埋点 SDK 在收集埋点数据的时候不再取组件的名字而是取组件上的 属性。
埋点配置发布后,用户在使用我们的产品时,SDK 会同步配置文件,并根据配置文件匹配用户的行为进行数据上报。
2.4 埋点上报
当用户打开页面时,SDK 首先会去远程拉取最新的埋点配置文件,此时又存在一个问题:拉取埋点配置文件是需要时间的,这就导致这个过程中用户的行为事件全部都会丢失。

如上图所示,为了解决这个问题,我们设计了一个队列,该队列会不断地接收并存储所有用户的行为事件。然后,我们在 中进行处理,使用 的好处是可以在空闲的时候执行,因而不影响动画及用户输入等关键事件。
当发现配置文件拉取成功时,会开始消费队列中的用户行为事件,如果用户行为事件对应的组件不能在配置文件中找到,则直接丢弃;否则,会对其进行处理。处理方法同埋点配置过程类似,首先也会通过 树收集到埋点属性数据来源集合,然后通过该集合给埋点配置中 中的字段进行赋值,最后合并 中的数据进行上报。
比如下面这条埋点配置:
{
"item-button": {
"constant": {
"operation": "click"
},
"variable": {
"title": "props.Item.title"
}
}
}
最后会生成如下所示的上报数据:
{
"operation": "click",
"title": "Second Item"
}
3. 总结
本文介绍了一套在 React Native 应用中实施可视化埋点的方案,实现这一套方案涉及到以下知识:
React 高阶组件的思想,通过对 React Native 组件进行重写,添加我们埋点相关的逻辑;
通过类组件的 可获取对应的 节点;
相关的属性,比如可以通过 、、 三个指针来对 树进行遍历, 和 可以用来替代组件的 和 等;
使用 babel 插件对代码进行改写,解决组件名称被混淆的问题。
这些知识有些是一些业界比较成熟的方案,可以直接复用,有些在官方文档中并未提及,需要对内部机制有深入的了解才能实现。由此可见,在进行业务开发时,保持对日常所用框架及工具的深入探索是必不可少的。
目前我们已成功接入了一些新的埋点需求。从开发反馈来看,不用写很多重复的埋点上报代码确实是一大福音,同时也可以支持不修改代码来修改或增加埋点,比较显著地提高了埋点需求上线的效率。我们也在不断改进这一系统,比如对埋点的检查及监控,检查的目的是确保上报数据的准确性,而监控的目的是及时发现埋点问题并进行修复。
参考链接
本文作者
Shopee 本地生活前端团队
加入我们
Shopee 本地生活(Digital Purchase & Local Services)团队业务涉及数字商品的售卖服务,包括话费、游戏充值等生活缴费,电影、演出等娱乐票务,以及火车票、飞机票、公交票等出行 OTA 服务。我们已经覆盖东南亚日常大部分数字商品消费场景,市场规模庞大,具有交易频度高、用户黏度高等特性。我们还为用户提供便捷的本地生活服务,包括餐饮、娱乐、购物等,这些都是线下支付的重要场景和流量入口。目前服务已覆盖大部分东南亚市场,用户规模庞大且进一步扩展。
目前团队大量岗位持续招聘中,海量 HC 涵盖后端、前端、测试等,感兴趣的同学可将简历发送至 amanda.lin@shopee.com(亦可进行咨询,注明来自 Shopee 技术博客)。
