前端數據上報

前言

最近接到一個需求,需要統計頁面的相關數據,並進行上報,本文就介紹一下數據上報的一些方法。

上報數據的時機

  • 頁面加載時

此時進行數據上報,只需要在頁面 load 時上報即可。

window.addEventListener('load', reportData, false);
  • 頁面卸載或頁面刷新時

此時進行數據上報,只需要在頁面 beforeunload 時上報即可。

window.addEventListener('beforeunload', reportData, false);
  • SPA 路由切換時

    • 如果是 vue-routerreact-router@3 及以下版本,則可以在 hooks 裏進行上報操作。
    • 如果是 react-router@4 則需要在 Routes 根組件的生命週期內進行上報。
  • 頁面多個 tab 切換時

如果是這種情況,可以在 visibilitychange 時通過讀取 document.visibilityStatedocument.hidden 區分頁面 tab 的激活狀態,判斷是否需要進行上報。

document.addEventListener("visibilitychange", function() {
  if(document.visibilityState === 'visible') {
    reportData();
  }
  if(document.visibilityState === 'hidden') {
    reportData2();
  }
  // your code ...
});

上報數據的方法

1. 直接發請求上報

我們可以直接將數據通過 ajax 發送到後端,以 axios 爲例。

axios.post(url, data);

但這種方法有一個問題,就是在頁面卸載或刷新時進行上報的話,請求可能會在瀏覽器關閉或重新加載前還未發送至服務端就被瀏覽器 cancel 掉,導致數據上報失敗。

我們可以將 ajax 請求改爲同步方法,這樣就能保證請求一定能發送到服務端。由於 fetchaxios 都不支持同步請求,所以需要通過 XMLHttpRequest 發送同步請求。

const syncReport = (url, { data = {}, headers = {} } = {}) => {
  const xhr = new XMLHttpRequest();
  xhr.open('POST', url, false);
  xhr.withCredentials = true;
  Object.keys(headers).forEach((key) => {
    xhr.setRequestHeader(key, headers[key]);
  });
  xhr.send(JSON.stringify(data));
};

這裏要注意的是,將請求改爲同步以後,會阻塞頁面關閉或重新加載的過程,這樣就會影響用戶體驗。

2. 動態圖片

我們可以通過在 beforeunload 事件處理器中創建一個圖片元素並設置它的 src 屬性的方法來延遲卸載以保證數據的發送,因爲絕大多數瀏覽器會延遲卸載以保證圖片的載入,所以數據可以在卸載事件中發送。

const reportData = (url, data) => {
  let img = document.createElement('img');
  const params = [];
  Object.keys(data).forEach((key) => {
    params.push(`${key}=${encodeURIComponent(data[key])}`);
  });
  img.onload = () => img = null;
  img.src = `${url}?${params.join('&')}`;
};

此時服務端可以返回一個 1px * 1px 的圖片,保證觸發 imgonload 事件,但如果某些瀏覽器在實現上無法保證圖片的載入,就會導致上報數據的丟失。

3. sendBeacon

爲了解決上述問題,便有了 navigator.sendBeacon 方法,使用該方法發送請求,可以保證數據有效送達,且不會阻塞頁面的卸載或加載,並且編碼比起上述方法更加簡單。

用法如下:

navigator.sendBeacon(url, data);

url 就是上報地址,data 可以是 ArrayBufferViewBlobDOMStringFormdata,根據官方規範,需要 request header 爲 CORS-safelisted-request-header,在這裏則需要保證 Content-Type 爲以下三種之一:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

我們一般會用到 DOMString , BlobFormdata 這三種對象作爲數據發送到後端,下面以這三種方式爲例進行說明。

  • DOMString

如果數據類型是 string,則可以直接上報,此時該請求會自動設置請求頭的 Content-Typetext/plain

const reportData = (url, data) => {
  navigator.sendBeacon(url, data);
};
  • Blob

如果用 Blob 發送數據,這時需要我們手動設置 Blob 的 MIME type,一般設置爲 application/x-www-form-urlencoded

const reportData = (url, data) => {
  const blob = new Blob([JSON.stringify(data), {
    type: 'application/x-www-form-urlencoded',
  }]);
  navigator.sendBeacon(url, blob);
};
  • Formdata

可以直接創建一個新的 Formdata,此時該請求會自動設置請求頭的 Content-Typemultipart/form-data

const reportData = (url, data) => {
  const formData = new FormData();
  Object.keys(data).forEach((key) => {
    let value = data[key];
    if (typeof value !== 'string') {
      // formData只能append string 或 Blob
      value = JSON.stringify(value);
    }
    formData.append(key, value);
  });
  navigator.sendBeacon(url, formData);
};

注意這裏的 JSON.stringify 操作,服務端需要將數據進行 parse 才能得到正確的數據。

總結

我們可以使用 sendBeacon 發送數據,這一方法既能保證數據可靠性,也不影響用戶體驗,如果瀏覽器不支持該方法,則可以降級使用同步的 ajax 發送數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章