小程序埋点

2,136 阅读3分钟

起步

当业务上线后,用户经常反馈无法支付,或者点击区域之后,无法获得对应的数据。

抑或是,为了分析出那些区域、模块点击的多,以及分析用户画像诸如此类的业务,才有此业务。

手动埋点

在页面中手动埋点不仅费时,更加费力,甚至会污染业务代码。

最常见的埋点的代码是这样子的:

<template>
    <view>
        <button @click.stop="tracker">x</button>
    </view>
</tempale>

<script>
export default {
    methonds:{
        tracker(e){
            // Do some work code.
           fetch("/report",e);
        }
    }
}
</script>

这样的代码简单易懂,但是将其难以管理。如果后期出现了需要埋点的页面、或者移除埋点地方,那么就会导致漫长的时间,需要修改代码。


自动埋点

修改 App

uni-app中,App.vue文件可以作为应用启动文件。它包含了onLaunchonShowonHideonError四种生命周期。

分别对应的是:

  • 小程序初始化
  • 小程序启动或切到前台
  • 小程序切后台

还有一个选项就是globalData,它是全局数据,它能让所有的页面都可以访问到其数据。

App.vue内的代码是典型的 Vue风格的代码,如下:

<script>
  export default {
    onLaunch: function() {
      console.log('App Launch');
    },
    onShow: function() {
      console.log('App Show');
    },
    onHide: function() {
      console.log('App Hide');
    }
  };
</script>

<style></style>

不过我们可以将它改造一下,让它变成可委托的形式:

const __entrustApp = App;
App = app => {
  ['onLounch', 'onShow', 'onHide', 'onError'].forEach(lifeCycle => {
    app[lifeCycle] = () => {
      console.log('--->lifeCycle', lifeCycle);
    };
  });
  __entrustApp(app);
};

这样,就可以看到各个生命周期的日志了。

修改 Page

同理,Page也是可以改写为委托的形式,和App相似,依旧可以委托。

const __entrustPage = Page;
Page = page => {
  ['onLoad', 'onShow', 'onReady', 'onHide'].forEach(lifeCycle => {
    page[lifeCycle] = () => {
      // Do something...
    };
  });
  return __entrustPage(page);
};

但是,uni-app高度封装了Page代码,也做了一系列的埋点:

function initHook(name, options) {
  const oldHook = options[name];
  if (!oldHook) {
    options[name] = function() {
      initTriggerEvent(this);
    };
  } else {
    options[name] = function(...args) {
      initTriggerEvent(this);
      return oldHook.apply(this, args);
    };
  }
}

Page = function(options = {}) {
  initHook('onLoad', options);
  return MPPage(options);
};

但是为了获取页面内的埋点信息,考虑下面的代码:

<template>
  <view>
    <button @click.stop="entrustEvent">Clike me</button>
  </view>
</template>

<script>
export default {
  methonds: {
    entrustEvent(e) {}
  }
};
</script>

使用Mixin

其中entrustEvent中的e参数代表了该元素传递过来的信息。

既然如此,那么可以使用mixin重新改写一下此函数。

export default {
  methonds: {
    initTracker() {
      const __entrstNativeEvent = this['entrustEvent'];
      this['entrustEvent'] = e => {
        console.log('Event before launch');
        __entrstNativeEvent(e);
        console.log('Event after launch');
      };
    }
  }
};

Event beforeEvent after分别原生的代码的之前和之后运行的日志。

此时,我们需要将需要埋点的函数和页面写入到埋点器中,并且作为一个全局mixin函数内嵌到Vue中:

class Tracker {
  constructor(trackerOpt) {
    this.fns = trackerOpt.fns;
    Vue.mixin(this.initMixin);
  }
  initMixin() {
    const { fns } = this;
    return {
      onLoad() {
        this.hijack();
      },
      methonds: {
        hijack() {
          let that = this;
          fns.forEach(element => {
            const __entrustFn = that[element];
            that[element] = event => {
              console.log('Before run.');
              __entrustFn(event);
              console.log('After run.');
            };
          });
        }
      }
    };
  }
}

其中这一块代码和上面原理的相似,只不过传入了函数fns参数,让它自动拦截函数。

每次触发函数时,就可以返回函数的埋点信息了。

  • event参数如下:
{
  "changedTouches": [{}],
  "currentTarget": { "id": "", "offsetLeft": 0, "offsetTop": 0, "dataset": {} },
  "detail": { "x": 262, "y": 68 },
  "mark": {},
  "mp": {
    "type": "tap",
    "timeStamp": 9779,
    "target": {},
    "currentTarget": {},
    "mark": {}
  },
  "mut": false,
  "preventDefault": {},
  "stopPropagation": {},
  "target": {
    "id": "",
    "offsetLeft": 0,
    "offsetTop": 0,
    "dataset": {},
    "x": 262
  },
  "timeStamp": 9779,
  "touches": [{}],
  "type": "tap"
}

其中,detail参数是该函数,以及关于其他数据。再加上一个函数接受它的参数,以便于上报。

只需要加上如下代码就可以了:

class Tracker {
  constructor(trackerOpt) {
    this.fns = trackerOpt.fns;
    this.reportFn = trackerOpt.reportFn;
    Vue.mixin(this.initMixin);
  }
  initMixin() {
+    const { fns, reportFn } = this;
    return {
      onLoad() {
        this.hijack();
      },
      methonds: {
        hijack() {
          let that = this;
          fns.forEach(element => {
            const __entrustFn = that[element];
            that[element] = event => {
              console.log('Before run.');
              __entrustFn(event);
+              reportFn.call(null,event);
              console.log('After run.');
            };
          });
        }
      }
    };
  }
}

这样,外层的reportFn就可以直接接收到埋点的数据。