本文首发于微信公众号“Shopee 技术团队”。
1. 背景
笔者所在团队为 Shopee 的本地生存前端团队,用户能够在咱们的平台购买优惠券,而后去线下门店应用。随着用户规模一直减少,钻研用户行为数据能够更好地领导产品功能设计,提供更加优良的用户体验。用户行为数据的钻研首先波及到如何采集,即咱们常说的“埋点”。
始终以来,咱们我的项目中的埋点都采纳代码埋点,每次新增埋点往往是一些重复性的工作,且须要从新公布代码能力失效,为此咱们的开发人员叫苦不迭。为了实现在不批改代码的前提下新增埋点,咱们调研了可视化埋点和无埋点两种形式。其中,无埋点(又称全埋点)会收集用户在利用里的所有行为,并上报所有相干的数据,由此产生大量无用数据,于是被咱们排除了。
而可视化埋点的形式为: 通过埋点平台圈选所需埋点的页面元素,进行埋点上报属性的配置与公布,由采集 SDK 同步埋点配置,并依据配置主动进行用户行为数据的采集和发送 。正好能够解决咱们的问题,因而咱们决定采纳可视化埋点计划。
在开始介绍咱们的零碎前,先来看看在 Web 上进行可视化埋点的基本思路:以点击事件为例(下文如果没有非凡阐明,均以点击事件为例),Web 可视化埋点个别会提供一个 SDK,SDK 会在 document
下面监听 click
事件,借助于事件委托的个性,能够捕捉到页面上任意元素的 click
事件及元素的信息。同时 Web 可视化埋点会提供一个平台,该平台通过 iframe
嵌入须要进行埋点配置的网页,而后通过 postMessage
来进行平台与指标页面的通信。
因为咱们的前端技术栈是 React Native,很多中央实现起来都比拟有难度,比方无奈通过 iframe
嵌入页面及 postMessage
实现平台与指标页面的通信,无奈借助事件委托的个性来实现咱们的 SDK 等。那么,最初到底是如何实现的呢?下文将具体开展介绍。
2. 零碎介绍
上面依照应用流程来介绍咱们的零碎。首先,须要在 React Native 客户端接入咱们的 SDK。
2.1 客户端接入 SDK
如下所示,咱们通过执行 SDK 的 initGoblin
办法导出了 TouchableComponent
,该对象又导出了跟点击相干的一些组件供业务方应用,咱们间接应用导出的这些点击相干的组件,并指定 trackId
即可(对于 trackId
后文会做介绍):
import {initGoblin} from '@dp/goblin-sdk-react-native'
export const {TouchableComponent} = initGoblin({...})
const {
GButton,
GTouchableHighlight,
GTouchableNativeFeedback,
GTouchableOpacity,
GTouchableWithoutFeedback
} = TouchableComponent
<GButton trackId="button">Click Me</GButton>
这些导出的组件都是利用高阶组件的思维对原来的组件进行了重写,并退出了埋点相干的逻辑。
2.2 连贯客户端与可视化埋点平台
接入完 SDK 后,接下来就能够对埋点进行配置了。进行埋点配置前,首先要将咱们的 React Native 客户端跟可视化埋点平台连接起来。
如上图所示,埋点配置人员首先须要在可视化埋点平台开始一个埋点工作,可视化埋点平台前端会通过 WebSocket
连贯到服务端,服务端会生成一个 sessionId
发送给前端:
并且会将连贯到服务端的 WebSocket
客户端进行注销:
{
25089: {creator: adminWSClient}
}
此时埋点配置人员在 React Native 客户端通过 SDK 提供的工具进入连贯页面,输出 sessionId
后通过 WebSocket
连贯到埋点可视化平台服务端:
服务端也会将连贯到的 WebSocket
客户端进行注销:
{
25089: {
creator: adminWSClient,
connector: rnWSClient
}
}
这样,通过可视化埋点平台服务端,就能够将 React Native 客户端同可视化埋点平台前端间接地连接起来了。此时,可视化埋点服务端会告诉前端和 React Native 客户端连贯胜利。失去音讯后,前端会进入配置页面,React Native 客户端则进入配置模式。之后每当配置人员在 React Native 客户端对页面元素进行圈选时,SDK 都会将相干数据发送到可视化埋点平台前端,供配置人员进行配置。
2.3 埋点配置
以下是连贯胜利后 React Native 客户端及可视化埋点前端对应的成果:
如图所示,当埋点配置人员在 React Native 客户端点击抉择所须要埋点的元素时,SDK 会高亮该元素。同时,SDK 还会将以后所选元素的 trackId
及埋点属性数据起源汇合发送到平台服务端,其中埋点属性数据起源汇合由元素对应的 React 组件自身和其先人组件的 props
和 state
属性所组成。
此时埋点配置人员可在平台上新增须要上报的字段并指定字段名、字段值起源, 比方图中新增了一个名为 title
的字段,并指定其值来自于 Item
这个组件 props
下的 title
属性 。
上文所说的 trackId
是以后所抉择元素的惟一标识,相似于 Web 中页面元素的 id 或 XPath。其中 id 的劣势是比拟精确,不会因为页面构造变动而生效,毛病是须要开发人员当时设定,而 XPath 的劣势是能够主动生成,然而对页面构造变动比拟敏感。
咱们晓得,每个 React 利用背地其实都对应着一颗由 FiberNode
节点组成的树,而 React 类组件中能够通过 this._reactInternals
(16 版本)失去以后组件所对应的 FiberNode
节点:
通过从以后组件的 FiberNode
登程始终往上遍历到根部,能够失去一条相似于 XPath 的门路作为该组件的 trackId
。然而在施行的时候发现雷同的代码在 Android 和 iOS 两个平台生成的 trackId
并不一样,这也就意味着如果采取这种计划的话,埋点配置时须要针对两个平台别离配置,这显然会大大增加工作量。所以最初咱们不得已放弃了该计划,临时采纳了开发手动给组件设置 trackId
的计划。
在遍历的同时,咱们还能够能够失去所有先人组件的 FiberNode
上的 memoizedProps
和 memoizedState
,它们别离对应组件的 props
和 state
,这样咱们就能够失去组件的埋点属性数据起源汇合了,相似于下图所示:
埋点配置实现后,会公布成 JSON 格局的文件,比方上文的例子公布后会如下所示:
{
...
"item-button": {
"constant": {"operation": "click"},
"variable": {"title": "props.Item.title"}
}
...
}
每一个配置都是以 trackId
为 key 的一个对象,其中对象中 constant
属性示意须要上报的字段的值是固定的,例如 operation
为 click
示意以后用户的操作为点击,variable
则示意须要上报的字段的值是动静的,其值是一条取值门路,这里示意 title
这个字段的值须要从 Item
组件的 props
中的 title
属性来获取。
然而在理论应用时又遇到了一个问题:咱们的代码在生产环境中打包当前,组件的名称都被混同了,导致配置人员进行配置的时候根本无法辨认。
为了解决这个问题,咱们参考 babel-plugin-add-react-displayname 库编写了一个 babel 插件,在打包的时候主动给组件增加 displayName
,埋点 SDK 在收集埋点数据的时候不再取组件的名字而是取组件上的 displayName
属性。
埋点配置公布后,用户在应用咱们的产品时,SDK 会同步配置文件,并依据配置文件匹配用户的行为进行数据上报。
2.4 埋点上报
当用户关上页面时,SDK 首先会去近程拉取最新的埋点配置文件,此时又存在一个问题:拉取埋点配置文件是须要工夫的,这就导致这个过程中用户的行为事件全副都会失落。
如上图所示,为了解决这个问题,咱们设计了一个队列,该队列会一直地接管并存储所有用户的行为事件。而后,咱们在 requestIdleCallback
中进行解决,应用 requestIdleCallback
的益处是能够在闲暇的时候执行,因此不影响动画及用户输出等要害事件。
当发现配置文件拉取胜利时,会开始生产队列中的用户行为事件,如果用户行为事件对应的组件不能在配置文件中找到,则间接抛弃;否则,会对其进行解决。解决办法同埋点配置过程相似,首先也会通过 FiberNode
树收集到埋点属性数据起源汇合,而后通过该汇合给埋点配置中 variable
中的字段进行赋值,最初合并 constant
中的数据进行上报。
比方上面这条埋点配置:
{
"item-button": {
"constant": {"operation": "click"},
"variable": {"title": "props.Item.title"}
}
}
最初会生成如下所示的上报数据:
{
"operation": "click",
"title": "Second Item"
}
3. 总结
本文介绍了一套在 React Native 利用中施行可视化埋点的计划,实现这一套计划波及到以下常识:
- React 高阶组件的思维,通过对 React Native 组件进行重写,增加咱们埋点相干的逻辑;
- 通过类组件的
_reactInternals
可获取对应的FiberNode
节点; FiberNode
相干的属性,比方能够通过child
、return
、sibling
三个指针来对FiberNode
树进行遍历,memoizedProps
和memoizedState
能够用来代替组件的props
和state
等;- 应用 babel 插件对代码进行改写,解决组件名称被混同的问题。
这些常识有些是一些业界比拟成熟的计划,能够间接复用,有些在官网文档中并未提及,须要对外部机制有深刻的理解能力实现。由此可见,在进行业务开发时,放弃对日常所用框架及工具的深刻摸索是必不可少的。
目前咱们已胜利接入了一些新的埋点需要。从开发反馈来看,不必写很多反复的埋点上报代码的确是一大福音,同时也能够反对不批改代码来批改或减少埋点,比较显著地进步了埋点需要上线的效率。咱们也在不断改进这一零碎,比方对埋点的查看及监控,查看的目标是确保上报数据的准确性,而监控的目标是及时发现埋点问题并进行修复。
参考链接
- Web 可视化全埋点使用指南, 神策数据
- babel-plugin-add-react-displayname
- Higher-Order Components
- _reactInternals
- FiberNode
本文作者
Shopee 本地生存前端团队