Vue 项目声明式主动埋点

2023-03-01  本文已影响0人  VioletJack

公司系统需求加上埋点功能,用来统计各页面功能的使用情况。于是,结合网上资料以及之前使用埋点系统的经历,仔细研究研究。

调研

埋点分类

常见的埋点类型有三种

埋点目标

埋点 SDK 实现猜想

以我之前工作中用到过的埋点系统 GrowingIO 为例。我们可以通过它的 SDK 文档 来验证上面的理论。

我的埋点

方案选择

由于项目的埋点只需要记录一些指定的行为,所以全埋点方案被我 PASS 了。同时也没有必要另外写一个页面去做埋点的圈选,最终,选择了最简单粗暴地主动埋点。

主动埋点 1.0

一开始埋点其实很简单,通过在 JavaScript 代码中写埋点代码来进行实现。

定义一个埋点工具对象。

// logger.js
export default {
  ...,
  track(data) {
    const configInfo = this.getConfigInfo() // 一些公共配置信息,如用户名、token、时间、url 等
    return fetch.post('/api/v1/web/log', {
      ...data,
      ...configInfo,
    })
  },
}

将 logger 对象绑到 Vue 的原型中。

// main.js
Vue.prototype.$logger = logger

在需要的地方主动埋点。

<template>
  <div>
    <el-button @click="download">download</el-button>
  </div>
</template>

<script>
  export default {
    name: 'demo',
    methods: {
      download() {
        window.open('file url')
        this.trackLogger()
      },
      trackLogger() {
        this.$logger.track({
          component_id: '2',
          component_name: '下载按钮',
        })
      },
    },
  }
</script>

<style lang="scss" scoped></style>

遇到的问题

其实主动埋点应该就是如此,但随着埋点代码的逐渐增多(已经从起初的 20 条增加到 203 条了……)。看代码的时候就非常难受了。描述一个场景:

声明式 vs 命令式

面对上面的场景,我在想有没有办法能够省去逐个查函数的步骤,让主动埋点代码更加直观呢。这里就得提到另外一个点了:声明式代码与命令式代码的区别了。

举几个栗子

比如画一幅画,用声明式的方式来描述是“我要画一幅画,它有青草、大树和天空”;而用命令式的方式描述是"我要画一幅画,首先需要画青草,然后再画大树,最后加上蓝色的天空。"

还有一个例子,在 vue 中有一个 createElement 函数,它可以在 vue 的 render 函数中命令式的创建 DOM 元素。

createElement(
  'anchored-heading',
  {
    props: {
      level: 1,
    },
  },
  [createElement('span', 'Hello'), ' world!'],
)

但这种命令式的写法可读性很差。vue 官方也发现了这个问题,于是引进了 JSX 来弥补这个缺陷。

import AnchoredHeading from './AnchoredHeading.vue'

new Vue({
  el: '#demo',
  render: function (h) {
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  },
})

JSX 的写法明显就更偏向于声明式。

那么回过来复习下主动埋点的目的:通过代码主动上报指定 DOM 元素的行为事件。所以个人感觉用声明式写法会更好一些。

主动埋点 2.0

说干就干,我试着将命令式埋点改为声明式埋点。

首先在入口文件 main.js 中引入全局注册逻辑。

// 事件名称
const COMPONENT_MAP = {
  1: '图表切换',
  2: '下载按钮',
}

// 修复点击子元素不上报埋点信息的问题
function bindDataset(el, value) {
  el.dataset.loggerId = value
  // 递归绑定 dataset 到所有子集上
  el.children.forEach((child) => {
    bindDataset(child, value)
  })
}

// 全局注册指令,在需要埋点的 DOM 上加上 dataset
Vue.directive('logger', {
  bind: function (el, binding) {
    const { value } = binding
    bindDataset(el, value)
  },
})

// 全局监听组件点击事件,加入防抖是为了避免短时间内快速重复点击
document.addEventListener(
  'click',
  throttle((e) => {
    if (e.target.dataset.loggerId) {
      this.$logger.track({
        component_id: e.target.dataset.loggerId,
        component_name: COMPONENT_MAP[e.target.dataset.loggerId],
      })
    }
  }, 2000),
)

在上面代码中,我将埋点通过vue 指令的方式将埋点信息绑定到目标 DOM 的 dateset 上面。然后通过全局 click 事件拦截来获得目标元素的点击行为,并上报埋点信息。

以上遇到的两个问题个人感觉不是最佳方案,如果有好的解决方案欢迎讨论呀!

使用方式如下,可读性上强了不少。

<div class="filter-wrap" @click="setFilterPopupVisible(true)" v-logger="1">
  <img class="filter-icon" :src="filterIconUrl" />
  <img
    class="filter-icon-checked"
    :src="filterSelectedIconUrl"
    v-show="isFilterActive"
  />
</div>

如此,以后在看埋点代码的时候只要全局搜索 v-logger 就可以很方便的看到有哪些 DOM 元素或者 vue 组件是进行了埋点的了。不需要反复去查各种事件了。

最后

折腾了一圈,主要就是想解决看主动埋点代码太恶心的问题。然后顺便复习一些知识点。

参考资料

上一篇下一篇

猜你喜欢

热点阅读