详解微信小程序(Taro)手动埋点和自动埋点的实现


Posted in Javascript onMarch 02, 2021

每一个公司要想用户增长,都要收集和分析用户操作数据,因此埋点是必不可少的事情。
而对于前端职业发展来说,传统的手动埋点,无疑是繁琐又无聊的事情,能简化就简化。

一、手动埋点

手动埋点就是在每一处需要的地方,都加一段上报埋点的代码。影响代码的阅读体验,且散落的埋点代码不方便管理。
以页面 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 方法来拦截事件回调的思路,但因元素唯一标识符不能自动获取,所以不大适合自动化埋点。

到此这篇关于详解微信小程序(Taro)手动埋点和自动埋点的实现的文章就介绍到这了,更多相关小程序手动埋点和自动埋点内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
JavaScript中的View-Model使用介绍
Aug 11 Javascript
js日历功能对象
Jan 12 Javascript
变量声明时命名与变量作为对象属性时命名的区别解析
Dec 06 Javascript
Jquery 在页面加载后执行的几种方式
Mar 14 Javascript
基于jquery实现鼠标滚轮驱动的图片切换效果
Oct 26 Javascript
easyui window refresh 刷新两次的解决方法(推荐)
May 18 Javascript
原生js和css实现图片轮播效果
Feb 07 Javascript
Bootstrap显示与隐藏简单实现代码
Mar 06 Javascript
详解用vue编写弹出框组件
Jul 04 Javascript
jquery实现垂直手风琴导航栏
Feb 18 jQuery
深入理解 ES6中的 Reflect用法
Jul 18 Javascript
vue-router路由懒加载及实现的3种方式
Feb 28 Vue.js
vue脚手架项目创建步骤详解
Mar 02 #Vue.js
javascript实现简单页面倒计时
Mar 02 #Javascript
javascript实现倒计时提示框
Mar 02 #Javascript
vue-cli中实现响应式布局的方法
Mar 02 #Vue.js
JavaScript实现筛选数组
Mar 02 #Javascript
js实现Element中input组件的部分功能并封装成组件(实例代码)
Mar 02 #Javascript
html5以及jQuery实现本地图片上传前的预览代码实例讲解
Mar 01 #jQuery
You might like
浅析memcache启动以及telnet命令详解
2013/06/28 PHP
那些年我们错过的魔术方法(Magic Methods)
2014/01/14 PHP
表单提交错误后返回内容消失问题的解决方法(PHP网站)
2015/10/20 PHP
php获取用户真实IP和防刷机制的实例代码
2018/11/28 PHP
javascript编程起步(第二课)
2007/01/10 Javascript
jquery1.4 教程二 ajax方法的改进
2010/02/25 Javascript
Js base64 加密解密介绍
2013/10/11 Javascript
javascript中parentNode,childNodes,children的应用详解
2013/12/17 Javascript
JS中的Replace方法使用经验分享
2015/05/20 Javascript
js判断文本框输入的内容是否为数字
2015/12/23 Javascript
js转html实体的方法
2016/09/27 Javascript
JavaScript仿支付宝6位数字密码输入框
2016/12/29 Javascript
详解微信小程序 template添加绑定事件
2017/06/23 Javascript
浅谈Vue 数据响应式原理
2018/05/07 Javascript
JavaScript实现数组全排列、去重及求最大值算法示例
2018/07/30 Javascript
vue-cli安装使用流程步骤详解
2018/11/08 Javascript
json 带斜杠时如何解析的实现
2019/08/12 Javascript
js prototype和__proto__的关系是什么
2019/08/23 Javascript
基于vue-cli3+typescript的tsx开发模板搭建过程分享
2020/02/28 Javascript
Python实现的石头剪子布代码分享
2014/08/22 Python
Python装饰器decorator用法实例
2014/11/10 Python
python Pygame的具体使用讲解
2017/11/03 Python
python图像常规操作
2017/11/11 Python
Django自定义用户认证示例详解
2018/03/14 Python
python中的不可变数据类型与可变数据类型详解
2018/09/16 Python
Python lambda表达式用法实例分析
2018/12/25 Python
简单了解python变量的作用域
2019/07/30 Python
详解使用Python下载文件的几种方法
2019/10/13 Python
详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)
2020/08/07 Python
CSS3实现伪类hover离开时平滑过渡效果示例
2017/08/10 HTML / CSS
css3 column实现卡片瀑布流布局的示例代码
2018/06/22 HTML / CSS
雷朋巴西官方商店:Ray-Ban Brasil
2020/07/21 全球购物
学生发电厂实习自我鉴定
2013/09/22 职场文书
法人委托书范本
2014/09/15 职场文书
学校师德师风整改措施
2014/10/27 职场文书
2014年就业工作总结
2014/11/26 职场文书