前言
最近接到一個需求炭玫,需要統(tǒng)計頁面的相關數(shù)據(jù)吞加,并進行上報叶圃,本文就介紹一下數(shù)據(jù)上報的一些方法盗似。
上報數(shù)據(jù)的時機
- 頁面加載時
此時進行數(shù)據(jù)上報,只需要在頁面 load
時上報即可。
window.addEventListener('load', reportData, false);
- 頁面卸載或頁面刷新時
此時進行數(shù)據(jù)上報闽瓢,只需要在頁面 beforeunload
時上報即可心赶。
window.addEventListener('beforeunload', reportData, false);
-
SPA 路由切換時
- 如果是
vue-router
或react-router@3
及以下版本,則可以在 hooks 里進行上報操作缨叫。 - 如果是
react-router@4
則需要在Routes
根組件的生命周期內(nèi)進行上報。
- 如果是
頁面多個 tab 切換時
如果是這種情況耻姥,可以在 visibilitychange
時通過讀取 document.visibilityState
或 document.hidden
區(qū)分頁面 tab 的激活狀態(tài)琐簇,判斷是否需要進行上報蒸健。
document.addEventListener("visibilitychange", function() {
if(document.visibilityState === 'visible') {
reportData();
}
if(document.visibilityState === 'hidden') {
reportData2();
}
// your code ...
});
上報數(shù)據(jù)的方法
1. 直接發(fā)請求上報
我們可以直接將數(shù)據(jù)通過 ajax 發(fā)送到后端似忧,以 axios
為例丈秩。
axios.post(url, data);
但這種方法有一個問題,就是在頁面卸載或刷新時進行上報的話饺著,請求可能會在瀏覽器關閉或重新加載前還未發(fā)送至服務端就被瀏覽器 cancel 掉,導致數(shù)據(jù)上報失敗瓶籽。
我們可以將 ajax 請求改為同步方法匠童,這樣就能保證請求一定能發(fā)送到服務端。由于 fetch
及 axios
都不支持同步請求塑顺,所以需要通過 XMLHttpRequest
發(fā)送同步請求汤求。
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. 動態(tài)圖片
我們可以通過在 beforeunload
事件處理器中創(chuàng)建一個圖片元素并設置它的 src
屬性的方法來延遲卸載以保證數(shù)據(jù)的發(fā)送裤唠,因為絕大多數(shù)瀏覽器會延遲卸載以保證圖片的載入挤牛,所以數(shù)據(jù)可以在卸載事件中發(fā)送。
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 的圖片种蘸,保證觸發(fā) img
的 onload
事件,但如果某些瀏覽器在實現(xiàn)上無法保證圖片的載入航瞭,就會導致上報數(shù)據(jù)的丟失诫硕。
3. sendBeacon
為了解決上述問題,便有了 navigator.sendBeacon 方法刊侯,使用該方法發(fā)送請求章办,可以保證數(shù)據(jù)有效送達,且不會阻塞頁面的卸載或加載滨彻,并且編碼比起上述方法更加簡單藕届。
用法如下:
navigator.sendBeacon(url, data);
url 就是上報地址,data 可以是 ArrayBufferView
亭饵,Blob
休偶,DOMString
或 Formdata
,根據(jù)官方規(guī)范冬骚,需要 request header 為 CORS-safelisted-request-header椅贱,在這里則需要保證 Content-Type
為以下三種之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
我們一般會用到 DOMString
, Blob
和 Formdata
這三種對象作為數(shù)據(jù)發(fā)送到后端,下面以這三種方式為例進行說明只冻。
- DOMString
如果數(shù)據(jù)類型是 string
庇麦,則可以直接上報,此時該請求會自動設置請求頭的 Content-Type
為 text/plain
喜德。
const reportData = (url, data) => {
navigator.sendBeacon(url, data);
};
- Blob
如果用 Blob
發(fā)送數(shù)據(jù)山橄,這時需要我們手動設置 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
可以直接創(chuàng)建一個新的 Formdata
航棱,此時該請求會自動設置請求頭的 Content-Type
為 multipart/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
操作,服務端需要將數(shù)據(jù)進行 parse 才能得到正確的數(shù)據(jù)饮醇。
總結(jié)
我們可以使用 sendBeacon
發(fā)送數(shù)據(jù)它抱,這一方法既能保證數(shù)據(jù)可靠性,也不影響用戶體驗朴艰,如果瀏覽器不支持該方法观蓄,則可以降級使用同步的 ajax 發(fā)送數(shù)據(jù)。