详解微信小程序(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跨域的4种方法和原理详解
Apr 08 Javascript
js创建表单元素并使用submit进行提交
Aug 14 Javascript
javascript实现可键盘控制的抽奖系统
Mar 10 Javascript
[原创]Bootstrap 中下拉菜单修改成鼠标悬停直接显示
Apr 14 Javascript
easyUI实现(alert)提示框自动关闭的实例代码
Nov 07 Javascript
js模拟支付宝密码输入框
Apr 11 Javascript
Angular实现一个简单的多选复选框的弹出框指令实例
Apr 25 Javascript
JS实现简单的选择题测评系统代码思路详解(demo)
Sep 03 Javascript
用js实现每隔一秒刷新时间的实例(含年月日时分秒)
Oct 25 Javascript
详细分析单线程JS执行问题
Nov 22 Javascript
微信小程序实现聊天对话(文本、图片)功能
Jul 06 Javascript
微信小程序聊天功能的示例代码
Jan 13 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 Mysql编程之高级技巧
2008/08/27 PHP
PHP 循环列出目录内容的函数代码
2010/05/26 PHP
将CKfinder整合进CKEditor3.0的新方法
2010/01/10 Javascript
Tinymce+jQuery.Validation使用产生的BUG
2010/03/29 Javascript
jQuery中调用WebService方法小结
2011/03/28 Javascript
jQuery获取Table某列的值(推荐)
2017/03/03 Javascript
详解bootstrap导航栏.nav与.navbar区别
2017/11/23 Javascript
一个简单的node.js界面实现方法
2018/06/01 Javascript
用POSTMAN发送JSON格式的POST请求示例
2018/09/04 Javascript
jQuery 获取除某指定对象外的其他对象 ( :not() 与.not())
2018/10/10 jQuery
vue根据条件不同显示不同按钮的操作
2020/08/04 Javascript
Vue左滑组件slider使用详解
2020/08/21 Javascript
js轮播图之旋转木马效果
2020/10/13 Javascript
[08:44]和酒神一起战斗 DOTA2教你做大人
2014/03/27 DOTA
利用Python开发实现简单的记事本
2016/11/15 Python
Python引用传值概念与用法实例小结
2017/10/07 Python
Python中的并发处理之asyncio包使用的详解
2018/04/03 Python
使用Python通过win32 COM实现Word文档的写入与保存方法
2018/05/08 Python
python cs架构实现简单文件传输
2020/03/20 Python
python实现二维插值的三维显示
2018/12/17 Python
解决Django 在ForeignKey中出现 non-nullable field错误的问题
2019/08/06 Python
django 解决model中类写不到数据库中,数据库无此字段的问题
2020/05/20 Python
浅谈django不使用restframework自定义接口与使用的区别
2020/07/15 Python
python 实现socket服务端并发的四种方式
2020/12/14 Python
Perfume’s Club意大利官网:欧洲美妆电商
2019/05/03 全球购物
AMAVII眼镜官网:时尚和设计师太阳镜
2019/05/05 全球购物
Maxpeedingrods美国:高性能汽车零件
2020/02/14 全球购物
如何启动时不需输入用户名与密码
2014/05/09 面试题
机电一体化专业应届本科生求职信
2013/09/27 职场文书
应届生人事助理求职信
2013/11/09 职场文书
化学教师教学反思
2014/01/17 职场文书
服务承诺书范文
2014/05/19 职场文书
给妈妈洗脚活动方案
2014/08/16 职场文书
党员干部学法用法心得体会
2016/01/21 职场文书
Go语言中break label与goto label的区别
2021/04/28 Golang
Linux中如何安装并部署Redis
2022/04/18 Servers