详解微信小程序(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 相关文章推荐
JS实现图片翻书效果示例代码
Sep 09 Javascript
window.onload和$(function(){})的区别介绍
Oct 30 Javascript
浅谈javascript中的call、apply、bind
Mar 06 Javascript
web 前端常用组件之Layer弹出层组件
Sep 22 Javascript
JQuery.dataTables表格插件添加跳转到指定页
Jun 09 jQuery
addeventlistener监听scroll跟touch(实例讲解)
Aug 04 Javascript
JSON创建键值对(key是中文或者数字)方式详解
Aug 24 Javascript
详解js类型判断
May 22 Javascript
AngularJS 前台分页实现的示例代码
Jun 07 Javascript
如何用JavaScript实现功能齐全的单链表详解
Feb 11 Javascript
详解使用Nuxt.js快速搭建服务端渲染(SSR)应用
Mar 13 Javascript
vue 判断两个时间插件结束时间必选大于开始时间的代码
Nov 04 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缓冲 output_buffering的使用详解
2013/06/13 PHP
joomla数据库操作示例代码
2016/01/06 PHP
PHP+JQuery+Ajax实现分页方法详解
2016/08/06 PHP
php实现微信公众号企业转账功能
2018/10/01 PHP
javascript下阻止表单重复提交、防刷新、防后退
2007/08/17 Javascript
js CSS操作方法集合
2008/10/31 Javascript
js三种排序算法分享
2012/08/16 Javascript
原生js实现给指定元素的后面追加内容
2013/04/10 Javascript
jQuery中使用data()方法读取HTML5自定义属性data-*实例
2014/04/11 Javascript
jQuery中not()方法用法实例
2015/01/06 Javascript
javascript实现复选框选中属性
2015/03/25 Javascript
轻松实现JavaScript图片切换
2016/01/12 Javascript
理解javascript中的MVC模式
2016/01/28 Javascript
学习使用bootstrap基本控件(table、form、button)
2016/04/12 Javascript
使用bootstrap validator的remote验证代码经验分享(推荐)
2016/09/21 Javascript
JavaScript 下载svg图片为png格式
2018/06/21 Javascript
JS函数节流和防抖之间的区分和实现详解
2019/01/11 Javascript
利用Vue实现一个markdown编辑器实例代码
2019/05/19 Javascript
在漏洞利用Python代码真的很爽
2007/08/26 Python
仅用50行Python代码实现一个简单的代理服务器
2015/04/08 Python
python操作ie登陆土豆网的方法
2015/05/09 Python
python实现校园网自动登录的示例讲解
2018/04/22 Python
numpy matrix和array的乘和加实例
2018/06/28 Python
Python简单I/O操作示例
2019/03/18 Python
Python 3.8新特征之asyncio REPL
2019/05/28 Python
Python+selenium点击网页上指定坐标的实例
2019/07/05 Python
python装饰器的特性原理详解
2019/12/25 Python
Python调用高德API实现批量地址转经纬度并写入表格的功能
2021/01/12 Python
DJI大疆德国官方商城:大疆无人机
2018/09/01 全球购物
电视购物广告词
2014/03/19 职场文书
企业宗旨标语
2014/06/10 职场文书
产品委托授权书范本
2014/09/16 职场文书
行政复议决定书
2015/06/24 职场文书
2015年教师节新闻稿
2015/07/17 职场文书
优秀毕业生主要事迹材料
2015/11/04 职场文书
企业廉洁教育心得体会
2016/01/20 职场文书