详解微信小程序(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 相关文章推荐
dess中一个简单的多路委托的实现
Jul 20 Javascript
js replace 与replaceall实例用法详解
Aug 03 Javascript
js使用数组判断提交数据是否存在相同数据
Nov 27 Javascript
Javascript前端UI框架Kit使用指南之kitjs的对话框组件
Nov 28 Javascript
dreamweaver 8实现Jquery自动提示
Dec 04 Javascript
超赞的动手创建JavaScript框架的详细教程
Jun 30 Javascript
JavaScript实现字符串与日期的互相转换及日期的格式化
Mar 07 Javascript
JS只能输入正整数的简单实例
Oct 07 Javascript
Vue数据驱动模拟实现4
Jan 12 Javascript
Vue表单验证插件Vue Validator使用方法详解
Apr 07 Javascript
angular2系列之路由转场动画的示例代码
Nov 09 Javascript
javascript原型链学习记录之继承实现方式分析
May 01 Javascript
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
PHP进程通信基础之信号
2017/02/19 PHP
php中青蛙跳台阶的问题解决方法
2018/10/14 PHP
PHP封装的page分页类定义与用法完整示例
2018/12/24 PHP
javascript计算星座属相(十二生肖属相)示例代码
2014/01/09 Javascript
html的DOM中Event对象onblur事件用法实例
2015/01/21 Javascript
iframe里使用JavaScript控制主页转向的方法
2015/04/03 Javascript
JScript中的条件注释详解
2015/04/24 Javascript
javascript日期计算实例分析
2015/06/29 Javascript
jQuery的Each比JS原生for循环性能慢很多的原因
2016/07/05 Javascript
bootstrap折叠调用collapse()后data-parent不生效的快速解决办法
2017/02/23 Javascript
jquery dataTable 获取某行数据
2017/05/05 jQuery
jQuery表单设置值的方法
2017/06/30 jQuery
JS实现数组简单去重及数组根据对象中的元素去重操作示例
2018/01/05 Javascript
Vue 自定义标签的src属性不能使用相对路径的解决
2019/09/17 Javascript
jquery实现淡入淡出轮播图效果
2020/12/13 jQuery
python查找指定具有相同内容文件的方法
2015/06/28 Python
Python 模块EasyGui详细介绍
2017/02/19 Python
对Python 获取类的成员变量及临时变量的方法详解
2019/01/22 Python
python2.7使用scapy发送syn实例
2020/05/05 Python
浅谈cv2.imread()和keras.preprocessing中的image.load_img()区别
2020/06/12 Python
Python基于字典实现switch case函数调用
2020/07/22 Python
全面解析CSS Media媒体查询使用操作(推荐)
2017/08/15 HTML / CSS
css3类选择器之结合元素选择器和多类选择器用法
2017/03/09 HTML / CSS
JAVA代码查错题
2014/10/10 面试题
关于递归的一道.NET面试题
2013/05/12 面试题
盛大二次面试题
2016/11/18 面试题
药剂学专业应届生自荐信
2013/09/29 职场文书
材料物理专业个人求职信
2013/12/15 职场文书
大学班长的职责
2014/01/27 职场文书
简历中的自我评价范文
2014/02/05 职场文书
顶撞老师检讨书
2014/02/07 职场文书
《宋庆龄故居的樟树》教学反思
2014/04/07 职场文书
校园文明倡议书
2014/05/16 职场文书
2014年语文教师工作总结
2014/12/18 职场文书
2016秋季幼儿园开学寄语
2015/12/03 职场文书
Python实现文本文件拆分写入到多个文本文件的方法
2021/04/18 Python