Vue手动埋点设计的方法实例
时间:2023-03-18 10:23:20|栏目:vue|点击: 次
目标
- 使用简单;
- 减少代码侵入性,不影响业务代码阅读
简述
- 埋点一般是按页面来管理的;
- 埋点的事件类型一般分为:点击、曝光和页面的进入和离开;
- 埋点的实质就是在恰当的时机去发送请求,上送业务参数
按页面管理埋点
在每个页面目录下创建events.js,管理当前页面的所有埋点事件。
为了减少埋点对业务代码的影响,events.js中每个埋点的设置都是一个方法,可以在这个方法中处理数据得到埋点需要上送的数据。
该埋点设置的方法,返回一个埋点需要上送的参数的对象。
// src/views/PageA/events.js export default { // 按事件类型管理 CLICK: { back(ctx, data) { let { param1, param2 } = data; // ...处理传入的数据 return { eventValue: '返回', elementId: 'pageA-返回', // 需要上送的处理后的业务参数 customData: { param1, param2 } }; } } }
遵循使用简单的原则,调用埋点
// src/views/PageA/index.vue this.$track('CLICK.back', data);
实现上面的调用
- 使用
require.context()
聚合各个页面目录下的埋点设置(events.js
)。 - 聚合后的埋点设置按页面作为模块管理,使用页面文件夹名称作为模块名。
- 结合路由管理,可以获得当前页面的埋点配置模块名。
- 在Vue.prototype下新增一个
$track()
方法。
// src/events/index.js import router from '@/router'; const ctx = require.context('@/views', true, /events\.js/); const configs = {}; ctx.keys().reduce((configs, path) => { if (/\.\/(\w+?)\/events\.js/.test(path)) { const moduleName = RegExp.$1; // 第一个子项 configs[moduleName] = resolveModule(moduleName, ctx(path)); } return configs; }, configs); function resolveModule(moduleName, module) { return Object.entries(module.default).reduce((all, [EVENT_TYPE, events]) => { all[EVENT_TYPE] = Object.keys(events).reduce((typeAll, key) => { typeAll[key] = buildTrackRequest( EVENT_TYPE.toLowerCase(), key, events[key] ); return typeAll; }, {}); }); } function buildTrackRequest(eventType, trackName, trackSetting) { return function trackRequest(...args) { // 看完后面再回过来看 if (typeof trackSetting !== 'function') { trackSetting = obj2fn(trackSetting); } // 执行用户定义的方法,返回埋点上送参数 const [success, result] = invokeUserFn(trackSetting.bind(this, {})); if (!success) return result; // 传入参数,发送埋点请求 return tracker(result); } } export function track(eventPath, ...args) { let event = configs; let seg; const segs = eventPath.split('.'); // 2段式 没有提供模块名,则需要去路由配置上取 if (segs.length === 2) { const moduleName = router.currentRoute.meta?.eventModule; if (!moduleName) { throwError(`${eventPath} 没有在路由配置中设置"meta.eventModule" 或者配置成3段式`); } event = event[moduleName]; } while ((seg = segs.shift())) event = event[seg]; if (!event) throwError(`${eventPath} 不存在`); // 给event绑定当前调用环境 return event.call(this, ...args); } function throwError(err) { throw Error(`[Track Error] ${err}`); } export default function install(Vue) { Vue.prototype.$track = track; }
埋点设置支持对象形式
很多时候,可能不需要上送业务参数,写成一个对象更加简单。
{ CLICK: { back: { eventValue: '返回', elementId: 'pageA-返回', } } }
有时候只需要上送简单的业务字段,无需额外处理,也想使用对象的形式。
支持{{param1}}模板语法,同vue-template用法。(param1是埋点调用组件的属性)
{ CLICK: { back: { eventValue: '返回', elementId: 'pageA-返回', customData: { param1: '{{param1}}' } } } }
将对象格式的埋点配置转成方法形式的
const templateRE = /\{\{(.+?)\}\}/g; // 处理对象形式的埋点设置 function obj2fn(obj) { return function() { const that = this; // 处理模板字符串 (function resolveObj(obj) { Object.keys(obj).forEach(key => { const val = obj[key]; // 解析模板字符串 if (typeof val === 'string' && templateRE.test(val)) { obj[key] = val.replace(templateRE, (...match) => { // js严格模式下无法执行with语法,以下是一种变通 return new Function(`with(this){return ${match[1]}}`).call(that); }); } // 递归处理 else if (isPlainObject(val)) resolve(val); }); })(obj); return obj; }; }
提供页面级别的参数设置
很多时候一个页面下的埋点都需要上送相同的参数,如页面名称等。
提供beforeModuleEach和afterModuleEach两个钩子。
一般使用beforeModuleEach设置模块(页面)通用的埋点参数,再合并单独的埋点设置参数,得到所有需要上送的参数。
function resolveModule(moduleName, module) { // 获取`events.js`文件中设置的钩子 const { beforeModuleEach, afterModuleEach } = module; // 获取动态设置钩子 const { beforeHooks, afterHooks } = getHooksByModule(moduleName); beforeModuleEach && beforeHooks.unshift(beforeModuleEach); afterModuleEach && afterHooks.unshift(afterModuleEach); return Object.entries(module.default).reduce((all, [EVENT_TYPE, events]) => { all[EVENT_TYPE] = Object.keys(events).reduce((typeAll, key) => { typeAll[key] = buildTrackRequest( EVENT_TYPE.toLowerCase(), key, events[key], beforeHooks, afterHooks ); return typeAll; }, {}); }); }