详解微信小程序(Taro)手动埋点和自动埋点
关注公众号 前端人,回复“加群”
添加无广告优质学习群
每一个公司要想用户增长,都要收集和分析用户操作数据,因此埋点是必不可少的事情。而对于前端职业发展来说,传统的手动埋点,无疑是繁琐又无聊的事情,能简化就简化。
一、手动埋点
手动埋点就是在每一处需要的地方,都加一段上报埋点的代码。影响代码的阅读体验,且散落的埋点代码不方便管理。
以页面 pv 为例,我们此前是在每一个页面中上报 pv:
// src/manual/home/index.tsx
import tracking from "./tracking";
// pageSn 是前端和产品约定的「页面在埋点系统的唯一标识」,比如这个项目首页的标识符是数字 11664
const pageSn = 11111;
export default () => {
// useDidShow 是 Taro 专有的 Hook,等同于小程序原生 componentDidShow 生命周期,会在页面展示的时候调用。
useDidShow(() => {
// 通过统一封装的 sendPv 方法发送 pv 埋点
sendPv(pageSn);
});
return <View>手动埋点页面</View>;
};
二、自动埋点
自动埋点可分为全自动埋点和半自动埋点。全自动埋点则是不管需不需要,将所有的点都埋了。前端肯定开心了 “以后埋点产品都不要不要找我啦”,可数据同学就哭唧唧了。
比如,腾讯和 Taro 团队共同推出 腾讯有数自动化埋点,接入超级简单。比如配置 proxyPage
为 true 即可 “上报所有页面的 browse 、leave、share 等事件”,配置 autoTrack
为 true 即可 “自动上报所有元素的 tap、change、longpress、confirm 事件”。
可从数据量和有效性来说,「全埋」等于「不埋」,因为「全埋」一方面对数据存储量要求很高,另一方面会给我们负责数据清洗的同学带来大量工作。
所以接下来,还是从中寻求平衡,着重看半自动埋点。
1、页面曝光(pv)
页面曝光(pv),理想的上报方式是:
在一个统一的地方(如 trackingConf.ts
),配置好每个要埋点的页面的标识符(即pageSn
)页面显示后,自动判断下是否需要上报(是否在 trackingConf.ts
配置文件中),要就直接上报。
具体实现
(1)统一配置埋点字段,pageSn
表示页面在埋点系统中的标识符
// trackingConf.ts
export default {
"auto/home/index": {
pageSn: 11111,
},
};
当然,如果你的业务允许三七二十一,上报所有页面 pv(带上 path 让产品自己筛选),那(1)这步可以省了,直接看(2),这种方式可称为「pv 全自动埋点」。
(2)封装 usePv
hook,在页面展示时,获取当前页面 pageSn
、判断是否要埋 pv、要的话发送 pv
// usePv.ts
// 获取当前页面 path,借助 Taro 的 getCurrentInstance
export const getPath = () => {
const path = Taro.getCurrentInstance().router?.path || "";
// 去掉开头的 /,比如将 '/auto/home/index' 改为 'auto/home/index'
return path.match(/^\/*/) ? path.replace(/^\/*/, "") : path;
};
// 获取当前页面 pageSn、判断是否要埋 pv、要的话发送 pv
// 入参 getExtra 支持携带额外参数
const usePv = ({
getExtra,
}: {
getExtra?: () => any;
} = {}) => {
// 页面曝光
useDidShow(() => {
const currentPath = getPath();
// 从 trackingConf 中获取 pageSn
const pageSn = trackingConf[currentPath]?.pageSn;
console.log("自动获取 pageSn", currentPath, pageSn);
if (pageSn) {
const extra = getExtra?.();
// 通过统一封装的 sendPv 方法发送 pv 埋点
extra ? sendPv(pageSn, extra) : sendPv(pageSn);
}
});
};
(3)然后封装页面组件 WrapPage
,使用上述的 usePv()
:
import React from "react";
import { View } from "@tarojs/components";
import usePv from "./usePv";
function WrapPage(Comp) {
return function MyPage(props) {
usePv();
return (
<View>
<Comp {...props} />
</View>
);
};
}
export default WrapPage;
(4)最后在所有页面组件,包一层 WrapPage
即可实现「所有页面按需埋点」:
// src/auto/home/index.tsx
const Index = WrapPage(() => {
return <View>自动埋点页面</View>;
});
后续新开发一个页面,除了用 WrapPage
包裹外,只需要在第(1)步的 trackingConf.ts
中增加该页面的 pageSn
即可。
提问环节
好奇宝宝们可能要问了:
(1)WrapPage
里这样封装了 usePv()
,应该如何支持上报自定义字段呢?
举个例子,产品希望 src/auto/home/index.tsx
这个页面上报 pv 的时候,额外上报一下 当前页面 URL 查询参数即 params
。
很简单,就是这个页面不要用 WrapPage 包裹,而是拿到 params 后直接调用 usePv 函数:
// src/auto/home/index.tsx
const Index = () => {
usePv({
getExtra: () => {
const params = Taro.getCurrentInstance().router?.params;
return { params };
},
});
return <View>自动埋点页面</View>;
});
(2)这里每个页面组件,都要用 WrapPage
包裹一下,对业务还是有侵入型了,原生小程序可以改写 Page
,在 Page 中直接 usePv()
。Taro 项目应该也可以这么做,实现 0 业务侵入吧?
Taro 项目中,确实可以也可以和原生小程序一样,在 App 中统一拦截原生 Page
,但这样的话,上面「某些页面要计算额外参数并上报」就不好解决了。
2、页面分享
微信小程序中,存在两种分享:
分享给好友: useShareAppMessage
。分享到朋友圈: useShareTimeline
。小程序基础库 v2.11.3 开始支持,目前只在 Android 平台可用。
具体实现
以 useShareAppMessage 为例(useShareTimeline 同理):
(1)仍在 trackingConf.ts
统一配置文件中,增加分享埋点的标识字段 eleSn
(及额外参数)
// trackingConf.ts
export default {
"auto/home/index": {
pageSn: 11111,
shareMessage: { eleSn: 2222, destination: 0 }, // 增加 shareMessage 包含分享好友的 eleSn、业务额外参数 destination
}
};
(2)封装 useShareAppMessage 方法,业务调用 Taro.useShareAppMessage
的地方全局替换为这个 useShareAppMessage
。
// 分享给好友,统一埋点
export const useShareAppMessage = ( callback: (payload: ShareAppMessageObject) => ShareAppMessageReturn ) => {
let newCallback = (payload: ShareAppMessageObject) => {
const result = callback(payload)
const currentPath = getPath(); // getPath 获取当前页面路径,可参考「1、页面曝光(pv)」中的 getPath
// 从 trackingConf 中获取 pageSn、shareMessage 等
const { pageSn, shareMessage } = trackingConf[currentPath]
const { eleSn, ...extra } = shareMessage || {}
let page_el_sn = eleSn
const { imageUrl: image_url, path: share_url } = result
const { from: from_ele } = payload
const reportInfo = {
from_ele,
share_to: 'friend', // 'friend' 表示分享给好友
image_url,
share_url,
...extra
}
console.log('...useShareAppMessage tracking', { pageSn, page_el_sn, reportInfo })
sendImpr(pageSn, page_el_sn, reportInfo) // 可自行封装 sendImpr 方法,发送分享埋点信息
return result
}
Taro.useShareAppMessage(newCallback)
}
这样,如果有个页面需增加分享好友的埋点,直接在 trackingConf.ts
中增加 shareMessage 的 eleSn
即可,useShareTimeline
同理。
提问环节
好奇宝宝们可能要问了:页面需要增加分享好友/朋友圈的埋点,可否 0 配置(即不用修改上述的 trackingConf.ts
文件)?
与前文中「pv 全自动埋点」类似,只要和产品约定好捞数据的方式也可以,比如笔者和产品约定了:
每个页面分享好友/朋友圈,eleSn 都是 444444,然后产品通过 pageSn
判断是哪个页面,通过 share_to
判断是分享好友 / 朋友圈,对于分享好友的场景,再通过 from_ele
判断通过右上角分享还是点击页面中的按钮分享。
这样页面分享也可以全自动埋点了。
3、元素埋点
元素自动埋点的调研遇到阻力,尚未落地。下文主要谈不同思路遇到的问题,有好的建议欢迎评论区沟通。
我们元素埋点,较高频的有曝光、点击事件,中低频的有滚动、悬停等事件。
手动埋点的方式就是在元素指定事件触发的时候,手动执行 sendImpr 上报埋点(带上页面唯一标识符 pageSn
、 元素唯一标识符 eleSn
)。
那这个环节是否可以省事一些呢?对业务无侵入,大概的做法还是:
在 Component
指定事件触发增加个 hook -> 判断是否要上报埋点 -> 满足条件则上报
问题一分为二:
(1)拦截元素事件回调
可以拦截并遍历小程序 Component
接收到的 options.methods
,如果是一个自定义函数,则在函数被调用的时候判断第一个参数(假设命名为 e)的 type 是否等于 tap
等事件。这时候可以根据 e 等信息决定是否满足埋点上报条件了。
原生小程序中的实现,大致如下:
// App.js
App({
onLaunch() {
let old = Component
Component = function(config) {
// 拦截业务传入的 config
const newConf = proxyConfig(config)
old(newConf)
}
}
})
const proxyConfig = function(conf) {
const methods = conf.methods
// 获取自定义方法(按需排除一些不埋点的方法)
let diyMethods = Object.entries(methods).filter(function (method) {
let methodName = method[0]
return ![
"onLoad",
"onShow",
"onReady",
"onHide",
"onUnload",
"onPullDownRefresh",
"onReachBottom",
"onPageScroll",
"onShareAppMessage",
"onResize",
"onTabItemTap",
"observer",
].includes(methodName);
})
diyMethods.forEach(function(method) {
const [methodName, methodFn] = method
// 修改 conf 中的 methods
methods[methodName] = function (...args) {
const e = args && args[0]
if (e && e.type === 'tap') {
console.log('...tapping', methodName, args) // 触发点击事件的时候,按需上报埋点
}
methodFn.call(this,...args)
}
});
// 返回修改后的 conf
return conf
}
Taro 项目中,不能直接在组件代码里用 Component
,但可以迂回一些的方式实现相同目的,比如:
// myProxy.js
module.exports = (function() {
let OriginPage = Page
let OriginComponent = Component
return (Page = function(conf) {
conf.forEach(function(e) {
let [methodName, methodFn] = e
if (typeof methodFn === 'function') {
conf[methodName] = function(...args) {
// 做你想做的事,如改写 conf 等
methodFn.call(this, ...args)
}
}
})
return OriginPage(conf)
})(
(Component = function(conf) {
const methods = conf.methods
methods.forEach(function(e) {
// 做你想做的事,如改写 conf 等
})
OriginComponent(conf)
})
)
})()
然后在 app.tsx
中直接引入 myProxy.js
即可
(2)如何自动生成元素唯一标识符
目前是通过埋点系统中申请下来的 eleSn
来唯一标识元素的,如果想要自动标识,可细分为:
XPath
:在 pc / mobile 中还可以,但在小程序中不支持直接获取节点的 XPath / 根据 XPath 获取节点。微信小程序可否支持通过 XPath 获取 DOM 元素?自动获取 组件方法名
:原生小程序中,因为直接拦截了 Component options 中的 methods,所以在事件触发时可以获取到原始的方法名,但 Taro 项目中不行,因为 methods 被代理了一道,事件触发后,你看到的方法名都是eh
。AST
解析源码分析出页面名、方法名和方法对应的注释来标识元素:Taro 项目中目测只能用这个方法,但成本较大,且「在代码不断迭代后,存量数据是否还能用」也是个问题,所以笔者未做尝试。
三、总结
本文概述了一下微信小程序(Taro)从手动埋点到自动埋点的思路。并按照页面埋点(pv、分享)以及元素埋点,分析了实现方式:
页面 pv: 封装 usePv
,根据当前页面 path 从配置文件中读取出 pageSn封装页面组件 WrapPage
调用usePv()
分享好友/朋友圈:自定义 useShareAppMessage、useShareTimeline,根据当前页面 path 从配置文件中读取出 pageSn 和分享 eleSn,然后获取传入参数后埋点上报 元素埋点:提供了改写 Component 方法来拦截事件回调的思路,但因元素唯一标识符不能自动获取,所以不大适合自动化埋点。
原文:juejin.cn/post/6934900764192030733
回复 资料包
领取我整理的进阶资料包回复 加群
,加入前端进阶群console.log("文章点赞===文章点在看===你我都快乐")
Bug离我更远了,下班离我更近了