欧美日韩国产一区,亚洲一区视频,色综合久久久久,私密按摩师舌头伸进去了,99re6这里只有精品,夜夜性日日交xxx性hd

taro微信小程序全埋點的思路與解決方案導(dǎo)向

  • • 發(fā)表于 5年前
  • • 作者 星河閱卷
  • • 12808 人瀏覽
  • • 0 條評論
  • • 最后編輯時間 5年前
  • • 來自 [開源項目]

原創(chuàng)聲明:本文為作者原創(chuàng),未經(jīng)允許不得轉(zhuǎn)載,經(jīng)授權(quán)轉(zhuǎn)載需注明作者和出處

背景

在一個比較成熟微信小程序中,為了統(tǒng)計每個頁面的行為,如統(tǒng)計頁面PV、UV、對頁面元素點擊等事件進(jìn)行監(jiān)聽,并且上報到我們自己的數(shù)據(jù)統(tǒng)計服務(wù)器上,目前網(wǎng)上能找到的大部分方案是通過手動埋點的方式實現(xiàn),這種方式效率較低,來一個頁面就要加一個統(tǒng)計邏輯,對代碼的侵入較多。且網(wǎng)上的方案都是基于原生微信小程序的解決方案,對于使用Taro進(jìn)行開發(fā)的項目來說,有點力不從心,因此,通過一段時間的研究與實驗,整理出這篇文章,用于梳理在使用Taro如何實現(xiàn)無侵入或低侵入(只需要在app.tsx中調(diào)用一個方法即可實現(xiàn)對所有的頁面生命周期進(jìn)行監(jiān)聽)。

GitHub項目

taro-track
歡迎star,如有任何疑問,歡迎提ISSUES進(jìn)行討論

思路

? 我們要想要實現(xiàn)無侵入或低侵入的監(jiān)控頁面聲明周期函數(shù),對于原生的微信小程序,我們可以參考網(wǎng)上的一個現(xiàn)有解決方案:小程序從手動埋點到自動埋點

? 其實現(xiàn)原理主要是:通過代理微信小程序的Page方法,在用戶傳遞進(jìn)來的生命周期鉤子函數(shù)外層包裝一層wrapper函數(shù),并在wrapper函數(shù)中實現(xiàn)統(tǒng)一數(shù)據(jù)上報的邏輯,然后再調(diào)用用戶定義的聲明周期鉤子函數(shù),這樣,使用者便可以在無感知的情況下進(jìn)行編碼,所有的數(shù)據(jù)收集與上報操作都可以在這個wrapper函數(shù)中執(zhí)行。

? 然而,上述方案僅適用于原生微信小程序,在基于Taro開發(fā)的微信小程序項目中,由于在Taro中所有單元都是組件Component,而非Page,經(jīng)過本人的反復(fù)試驗,Taro在運行的過程中,并沒有調(diào)用過Page方法,因此,通過代理微信原生Page方法這條路是行不通了。

? 那么,既然在Taro中一切皆組件,我們能不能通過代理Component實現(xiàn)類似的邏輯呢?經(jīng)過試驗,這個想法是可行的,不過由于Component的生命周期鉤子跟Page的生命周期鉤子不一樣,所以我們需要對其做一定的轉(zhuǎn)化。

具體實現(xiàn)

//// core/wx-tools.ts

/**
 * 獲取微信原生Page
 * @returns {WechatMiniprogram.Page.Constructor}
 */
export function getWxPage():WechatMiniprogram.Page.Constructor  {
  return Page;
}

/**
 * 重寫微信原生Page
 * @param newPage
 */
export function overrideWxPage(newPage: any):void {
  Page = newPage;
}

/**
 * 獲取微信原生App
 * @returns {WechatMiniprogram.App.Constructor}
 */
export function getWxApp():WechatMiniprogram.App.Constructor {
  return App;
}

/**
 * 重寫微信原生App
 * @param newApp
 */
export function overrideWxApp(newApp: any): void {
  App = newApp;
}

/**
 * 獲取微信原生Component
 * @returns {WechatMiniprogram.Component.Constructor}
 */
export function getWxComponent():WechatMiniprogram.Component.Constructor {
  return Component;
}

/**
 * 重寫微信原生Component
 * @param newComponent
 */
export function overrideWxComponent(newComponent: any): void {
  Component = newComponent;
}
//// overrideWxPage.ts
import { getWxComponent, getWxPage, overrideWxComponent, overrideWxPage } from '@kiner/core/es';

// 需要代理的生命周期鉤子,包含Page和Component的鉤子
const proxyMethods = [
  "onShow",
  "onHide",
  "onReady",
  "onLoad",
  "onUnload",
  "created",
  "attached",
  "ready",
  "moved",
  "detached",
];

// 觸發(fā)鉤子的回調(diào)函數(shù)中的初始化參數(shù)
export interface OverrideWechatPageInitOptions {
  __route__?: string
  __isPage__?: boolean
  [key:string]: any
}

// 觸發(fā)鉤子是調(diào)用的回調(diào)函數(shù)類型
export type OverrideWechatPageHooksCb = (method: string, options: OverrideWechatPageInitOptions)=>void;

// 用于存儲所有的回調(diào)函數(shù)
const pageHooksCbs: OverrideWechatPageHooksCb[] = [];

export class OverrideWechatPage {
  // 微信原生Page方法
  private readonly wechatOriginalPage: WechatMiniprogram.Page.Constructor;
  // 微信原生Component方法
  private readonly wechatOriginalComponent: WechatMiniprogram.Component.Constructor;
  // 是否使用taro框架
  private readonly isTaro = true;

  public constructor(isTaro:boolean=true) {
    this.isTaro = true;
    // 基于以后可能需要兼容頭條、百度小程序需要,將所有操作原生微信小程序的操都獨立抽離到單獨的模塊中進(jìn)行維護(hù),
    // 若以后需要兼容其他小程序,只需要在蓋某塊內(nèi)部進(jìn)行api動態(tài)指定切換即可
    // 保存微信原始Page對象,以便我們在銷毀時恢復(fù)原狀
    this.wechatOriginalPage = getWxPage();
    // 保存微信原始Component對象,以便我們在銷毀時恢復(fù)原狀
    this.wechatOriginalComponent = getWxComponent();
  }

  public initialize(pageHooksCb: OverrideWechatPageHooksCb): void {
    const _Page = getWxPage();
    const _Component = getWxComponent();
    // 將回調(diào)函數(shù)放入隊列中,在觸發(fā)原生生命周期鉤子時依次調(diào)用
    pageHooksCbs.push(pageHooksCb);

    console.info(`原始Page對象`, pageHooksCbs, this.wechatOriginalPage);

    const self = this;

    // 根據(jù)是否使用Taro框架篩選需要代理的鉤子函數(shù)
    // 若使用Taro則需代理組件的生命周期鉤子,若使用原生小程序則代理Page的生命周期鉤子
    const needProxyMethods = proxyMethods.filter(item=>this.isTaro?!item.startsWith('on'):item.startsWith('on'));

    /**
     * 實現(xiàn)代理Page|Component的邏輯
     * @param {OverrideWechatPageInitOptions} options
     * @returns {string}
     */
    const wrapper = function(options: OverrideWechatPageInitOptions){


      needProxyMethods.forEach(methodName=>{
        // 緩存用戶定義的生命周期鉤子
        const _originalHooks = options[methodName];
        const wrapperMethod = function (...args: any[]) {
          // 依次觸發(fā)頁面生命周期回調(diào)
          pageHooksCbs.forEach((fn: OverrideWechatPageHooksCb)=>fn(methodName, options));
          // 若用戶有定義該生命周期鉤子則執(zhí)行這個鉤子函數(shù)
          return _originalHooks&&_originalHooks.call(this, ...args);
        };
        // 重寫options,用新的包裝函數(shù)覆蓋原始鉤子函數(shù)
        options = {
          ...options,
          [methodName]:wrapperMethod
        };
      });

      // 使用新的options進(jìn)行初始化操作
      let res = "";
      if(self.isTaro){
        res = _Component(options);
      }else{
        _Page(options);
      }

      // 由于在Taro中,一切皆組件,我們需要知道當(dāng)前組件是頁面組件還是普通組件
      // 微信小程序原生的Component執(zhí)行構(gòu)造函數(shù)后會直接返回當(dāng)前組件的路徑,如:pages/index/index
      // 因此,我們可以將這個路徑保存在我們的wrapper中,方便我們在外部判斷當(dāng)前組件是否是頁面組件
      options.__router__ = wrapper.__route__ = res;
      options.__isPage__ = res.startsWith('pages/');

      console.info(`重寫微信小程序Page對象`, options, res);

      return res;
    };

    wrapper.__route__ = '';
    wrapper.__isPage__ = false;


    // 重寫微信原生Page|Component
    if(this.isTaro){
      overrideWxComponent(wrapper);
    }else{
      overrideWxPage(wrapper);
    }


  }

  /**
   * 重置微信原生方法
   */
  public destroy(): void {
    overrideWxPage(this.wechatOriginalPage);
    overrideWxComponent(this.wechatOriginalComponent);
  }
}
//// entry.ts

/**
 * 初始化微信小程序生命周期監(jiān)聽
 * @param {string | undefined} baseUrl     發(fā)送的日志服務(wù)器,默認(rèn)為生產(chǎn)服務(wù)
 * @param {TransporterType} transporter    采用的上傳通道方案是elk還是console
 * @param {string | undefined} appVersion  當(dāng)前小程序版本
 * @param {string | undefined} appName     當(dāng)前小程序的名稱
 * @param {boolean} showLog                發(fā)送成功是否打印日志
 * @param {number} pstInterval             applet-pst事件循環(huán)上報時間間隔,默認(rèn)為:5000
 * @param extraData                        額外參數(shù),sdk中無法直接獲取的字段,如appid等
 * @param {{[p: string]: string}} extraData
 */
export function initAppletLifecycleListener(
    {baseUrl,
      isTaro,
      transporter,
      appVersion,
      appName,
      showLog = false,
      pstInterval = 5000
    }: InitAppletLifecycleOption,
    extraData: { [key: string]: string } = {}
  ){
  const logger = Logger.create('initAppletLifecycleListener');
  const logStyle = 'background: green; color: #FFFFFF; padding: 5px 10px;';
  const tpr = initTransporter(transporter, {
    baseUrl: baseUrl,
    query: {
      app_name: appName,
      app_version: appVersion,
      ev_type: 'client_ub'
    }
  });

  let timer = null;


  const overrideWechatPage = new OverrideWechatPage(isTaro);

  const prevUrl = getWxCurrentHref();

  // 對頁面的onLoad和onReady進(jìn)行監(jiān)聽
  overrideWechatPage.initialize(async (methodName: string, options) => {
    if (!options.__isPage__) {
      return;
    }

    // const hooksName = CompAndPageHookMap[methodName];

    console.log(`dolphin-wx/entry:${methodName}-${CompAndPageHookMap[methodName]}`);
    const openTime = Date.now();
    const baseExtFields = getBaseExtFields(extraData);
    const baseFields = await getBaseFields(extraData);

    const extraExt = extraData.ext || {};

    function sendPv() {
      const now = Date.now();
      const sendData = {
        ev: 'applet-pv',
        ...baseFields,
        ...extraData,
        time: now,
        ext: {
          ...baseExtFields,
          ...extraExt,
          time: now - openTime
        }
      };

      tpr.send(sendData, () => showLog && logger.info(`%capplet-pv上報成功:`, logStyle, sendData));
    }

    function sendPst() {
      const now = Date.now();
      const sendPstData = {
        ev: 'applet-pst',
        ...baseFields,
        ...extraData,
        time: now,
        ext: {
          ...baseExtFields,
          ...extraExt,
          time: now - openTime,
          url: getWxCurrentHref()
        }
      };
      tpr.send(sendPstData, () => showLog && logger.info(`%capplet-pst上報成功:`, logStyle, sendPstData));
    }


    function sendPvOut() {
      const now = Date.now();
      const sendPvOutData = {
        ev: 'applet-pvout',
        ...baseFields,
        ...extraData,
        time: now,
        pl: baseFields.url,
        ext: {
          ...baseExtFields,
          ...extraExt,
          time: now - openTime,
          url: baseFields.url
        }
      };
      tpr.send(sendPvOutData, () => showLog && logger.info(`%capplet-pvout上報成功:`, logStyle, sendPvOutData));
    }


    // console.log(`dolphin-wx/entry[${methodName}]`);
    switch (methodName) {
      case proxyWxLifeHooks.onReady:
      case proxyWxLifeHooks.ready:
        // 若觸發(fā)onLoad或attached時當(dāng)前url與緩存的url不一樣,說明發(fā)生頁面跳轉(zhuǎn),觸發(fā)pvout
        if(prevUrl&&prevUrl!==getWxCurrentHref()){
          sendPvOut();
        }
        sendPv();
        timer = setInterval(() => {

          sendPst();

        }, pstInterval);
        break;
      case proxyWxLifeHooks.onUnload:
      case proxyWxLifeHooks.detached:
        sendPvOut();
        break;
    }
  });
}

最終實現(xiàn)效果

近期有一些朋友詢問我這種方法是否真實可行,再次說明一下,經(jīng)過本人在公司項目中實際應(yīng)用,證實是可行的,雖然不算相當(dāng)完善,但是是完全能達(dá)到我們的目的的。
注:由于taro是第三方框架,版本升級不可控,因此,我們只是對特定個版本,如2.1.5進(jìn)行兼容,其他版本尚未做兼容處理
在運行taro的編譯或開發(fā)命令時的輸出
在這里插入圖片描述

在線演示視頻

本項目已經(jīng)將核心功能抽離到github中,有興趣的朋友可以去看一下,歡迎star和issue
taro-track

分享到:
0條評論
Ctrl+Enter
作者

星河閱卷

星河閱卷

APP:4 帖子:7 回復(fù):20 積分:3660

已加入社區(qū)[3006]天

速度要快,動作要帥

作者詳情》
Top