知識
不管是網站,軟件還是小程序,都要直接或間接能為您產生價值,我們在追求其視覺表現(xiàn)的同時,更側重于功能的便捷,營銷的便利,運營的高效,讓網站成為營銷工具,讓軟件能切實提升企業(yè)內部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!
微信小程序(Taro)的自動埋點
發(fā)表時間:2021-1-5
發(fā)布人:葵宇科技
瀏覽次數(shù):43
在做各種各樣的業(yè)務時,我們不可避免的需要在業(yè)務中進行埋點,這些埋點通常包含但不限于曝光、點擊、停留時長、離開頁面等場景,而在小程序中因為其和瀏覽器不同的架構,導致了監(jiān)聽頁面變的更加困難,通常我們都會通過重寫 Page
方法來達到對小程序原生生命周期的攔截代理,從而進行業(yè)務埋點,但是在 Taro
中這一切變得不同了。
現(xiàn)狀
在多端統(tǒng)一的Taro
中,我們不再能看到顯式的 Page
調用,甚至 Taro
打包之后的代碼里也不再存在任何 Page
的跡象,取而代之的則是小程序原生的 Component
(這一點大家通過觀察打包后的內容可以得知),所以為了實現(xiàn)微信小程序在Taro
中的自動埋點,我們需要換一個策略:重寫Component
。
基本的重寫
在微信小程序中,其暴露的Component
和 Page
能夠直接被重寫并進行賦值:
const _originalComponent = Component;
const wrappedComponent = function (options) {
...do something before real Component
return _originalComponent(options);
}
復制代碼
這樣可以很快的解決問題,但是當我們在另一個小程序做這件事情的時候,我們就又需要手動做一次這些處理,難免有些麻煩,為什么不找一個更通用的方案,我們只用關注我們需要關注的業(yè)務(埋點)就行了呢?
解決方案
重中之重,從零開始思考,掌握真正問題,接近問題本質
根問題
在解決問題之前,不如讓我們先看看這個問題的本質是什么。想在小程序中進行自動的埋點,其實要做的就是在小程序指定的生命周期里做一些固定的處理,所以我們自動埋點的問題實際上是如何劫持小程序的生命周期,而要劫持小程序的生命周期,我們需要做的就是去重寫options
。
如何解決
在解決這個問題之前,我們要把自己需要解決的問題拆分出來:
- 應該怎么重寫
options
- 應該重寫哪些
options
- 怎樣把自己的業(yè)務注入到監(jiān)聽的生命周期中。
我們在上面的基礎解決辦法對如何重寫options
就已經有了答案,我們只需要在原小程序提供的方法外再包裹一層即可解決,同時為了保證我們的解決方案能適用于原生小程序和Taro
這種多端統(tǒng)一的小程序方案,我們應該同時支持重寫Component
和Page
,而對于最后一個問題,我們可以思考一下js
中的事件系統(tǒng),相似的我們也可以實現(xiàn)一套發(fā)布訂閱的邏輯,只需要定制觸發(fā)事件(生命周期)和listeners
,再針對生命周期原有邏輯進行包裝即可;
step 1
首先我們在重寫Component
和Page
之前應當保存原始的方法,避免原始方法被污染我們無法回退,這之后再去將小程序中的所有生命周期進行枚舉生成一個默認的事件對象中,保證我們在注冊了對應生命周期的listeners
后能通過尋址找到并對原生命周期方法進行重寫。
export const ProxyLifecycle = {
ON_READY: 'onReady',
ON_SHOW: 'onShow',
ON_HIDE: 'onHide',
ON_LOAD: 'onLoad',
ON_UNLOAD: 'onUnload',
CREATED: 'created',
ATTACHED: 'attached',
READY: 'ready',
MOVED: 'moved',
DETACHED: 'detached',
SHOW: 'show',
HIDE: 'hide',
RESIZE: 'resize',
};
public constructor() {
this.initLifecycleHooks();
this.wechatOriginalPage = getWxPage();
this.wechatOriginalComponent = getWxComponent();
}
// 初始化所有生命周期的鉤子函數(shù)
private initLifecycleHooks(): void {
this.lifecycleHooks = Object.keys(ProxyLifecycle).reduce((res, cur: keyof typeof ProxyLifecycle) => {
res[ProxyLifecycle[cur]] = [] as WeappLifecycleHook[];
return res;
}, {} as Record<string, WeappLifecycleHook[]>);
}
復制代碼
step 2
在這一步我們只需要將監(jiān)聽函數(shù)放到我們第一步中聲明的事件對象中,然后執(zhí)行重寫流程即可:
public addLifecycleListener(lifeTimeOrLifecycle: string, listener: WeappLifecycleHook): OverrideWechatPage {
// 針對指定周期定義Hooks
this.lifecycleHooks[lifeTimeOrLifecycle].push(listener);
const _Page = this.wechatOriginalPage;
const _Component = this.wechatOriginalComponent;
const self = this;
const wrapMode = this.checkMode(lifeTimeOrLifecycle);
const componentNeedWrap = ['component', 'pageLifetimes'].includes(wrapMode);
const wrapper = function wrapFunc(options: IOverrideWechatPageInitOptions): string | void {
const optionsKey = wrapMode === 'pageLifetimes' ? 'pageLifetimes' : '';
options = self.findHooksAndWrap(lifeTimeOrLifecycle, optionsKey, options);
const res = componentNeedWrap ? _Component(options) : _Page(options);
options.__router__ = (wrapper as any).__route__ = res;
return res;
};
(wrapper as any).__route__ = '';
if (componentNeedWrap) {
overrideWxComponent(wrapper);
} else {
overrideWxPage(wrapper);
}
return this;
}
/**
* 為對應的生命周期重寫options
* @param proxyLifecycleOrTime 需要攔截的生命周期
* @param optionsKey 需要重寫的 optionsKey,此處用于 lifetime 模式
* @param options 需要被重寫的 options
* @returns {IOverrideWechatPageInitOptions} 被重寫的options
*/
private findHooksAndWrap = (
proxyLifecycleOrTime: string,
optionsKey = '',
options: IOverrideWechatPageInitOptions,
): IOverrideWechatPageInitOptions => {
let processedOptions = { ...options };
const hooks = this.lifecycleHooks[proxyLifecycleOrTime];
processedOptions = OverrideWechatPage.wrapLifecycleOptions(proxyLifecycleOrTime, hooks, optionsKey, options);
return processedOptions;
};
/**
* 重寫options
* @param lifecycle 需要被重寫的生命周期
* @param hooks 為生命周期添加的鉤子函數(shù)
* @param optionsKey 需要被重寫的optionsKey,僅用于 lifetime 模式
* @param options 需要被重寫的配置項
* @returns {IOverrideWechatPageInitOptions} 被重寫的options
*/
private static wrapLifecycleOptions = (
lifecycle: string,
hooks: WeappLifecycleHook[],
optionsKey = '',
options: IOverrideWechatPageInitOptions,
): IOverrideWechatPageInitOptions => {
let currentOptions = { ...options };
const originalMethod = optionsKey ? (currentOptions[optionsKey] || {})[lifecycle] : currentOptions[lifecycle];
const runLifecycleHooks = (): void => {
hooks.forEach((hook) => {
if (currentOptions.__isPage__) {
hook(currentOptions);
}
});
};
const warpMethod = runFunctionWithAop([runLifecycleHooks], originalMethod);
currentOptions = optionsKey
? {
...currentOptions,
[optionsKey]: {
...options[optionsKey],
...(currentOptions[optionsKey] || {}),
[lifecycle]: warpMethod,
},
}
: {
...currentOptions,
[lifecycle]: warpMethod,
};
return currentOptions;
};
復制代碼
經過如上兩步,我們就能對指定的生命周期進行劫持并注入我們自己的listeners
,使用被重寫過Component
或者Page
就會自動觸發(fā)這些 listeners
。
weapp-lifecycle-hook-plugin
為了方便直接對微信小程序原生環(huán)境和Taro
等多端統(tǒng)一方案進行這一套通用的解決方案,我實現(xiàn)了一個插件來解決這個問題(私心安利)
安裝
npm install weapp-lifecycle-hook-plugin
或者
yarn add weapp-lifecycle-hook-plugin
復制代碼
使用
import OverrideWechatPage, { setupLifecycleListeners, ProxyLifecycle } from 'weapp-lifecycle-hook-plugin';
// 供 setupLifecycleListeners 使用的 hook 函數(shù),接受一個參數(shù),為當前組件/頁面的options
function simpleReportGoPage(options: any): void {
console.log('goPage', options);
}
// setupListeners
class App extends Component {
constructor(props) {
super(props);
}
componentWillMount() {
// ...
// 手動創(chuàng)建的實例和使用 setupLifecycleListeners 創(chuàng)建的實例不是同一個,所以需要銷毀時需要單獨對其進行銷毀
// 直接調用實例方式
const instance = new OverrideWechatPage(this.config.pages);
// 直接調用實例上的 addListener 方法在全局增加監(jiān)聽函數(shù),可鏈式調用
instance.addLifecycleListener(ProxyLifecycle.SHOW, simpleReportGoPage);
// setupListeners 的使用
setupLifecycleListeners(ProxyLifecycle.SHOW, [simpleReportGoPage], this.config.pages);
// ...
}
// ...
}
復制代碼
只需要通過簡單地 setup
就能解決以前需要手動書寫一大堆的重寫邏輯,何樂而不為呢
作者:santree
來源:掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。