最近接到一個需求,產品經理希望能新增彈窗廣告,廣告可根據後臺配置在應用任意頁面彈出展示。當後臺改變當前頁面廣告次數、連結或者目標頁後,當前頁面資料修改,不影響其他頁面資料
例如後臺設定“首頁”出現廣告 1 次,“我的”頁面廣告出現 3 次,使用者進去後關閉了“首頁”廣告 1 次,關閉了“我的”頁面廣告 2 次。此時退出應用,後臺將“首頁”廣告設定為 2 次,那麼該使用者“首頁”廣告重置為 2 次,“我的”頁面廣告仍為 1 次( 3 - 2)
需求分析
後端返回的資料必然是個陣列,每個物件中會有目標頁(展示的頁面),跳轉連結,總出現的次數三引數。前端要對資料進行處理:
- 當本地沒有資料時(第一次進入),將總出現次數賦值給一引數 firstTotalTimes(記錄原總出現次數)
當本地有資料(非第一次進入)
- 將本地儲存中的 firstTotalTimes 清除,返回值賦值為 removeLocalTotalTimeList
將 removeLocalTotalTimeList 與 請求返回的資料 advertisementList 進行對比
- 相等,說明後臺資料沒有改變,檢視你本地儲存中的總出現次數是否大於 0 ,大於則展示廣告
- 不相等,說明後臺修改了資料,這裡還要分析,只重置修改處頁的,未修改的地方不做處理
筆者用的框架是 umi3,其中有 wrappers 概念,即一個配置路由的高階元件封裝,在 umi.conf 中加上後,任何頁面都要先經過這一道。關鍵程式碼如下:
useEffect(() => {
dispatch({ type: 'common/fetchGetPopUpAdvertisementList' }).then((resData: any) => {
if (resData?.resultCode === "S00000") {
if (!localStorage.advertisementList) {
const addFirstTotalTimes = resData.advertisementList.map((item: any) => {
item.firstTotalTimes = item.totalTimes
return item;
})
localStorage.advertisementList = JSON.stringify(addFirstTotalTimes);
}
const localAdvertisementList = JSON.parse(localStorage.advertisementList)
const cloneLocalAdvertisementList = JSON.parse(JSON.stringify(localAdvertisementList))
const removeLocalTotalTimeList = cloneLocalAdvertisementList.map((item: any) => {
delete item.firstTotalTimes
return item
})
if (_.isEqual(removeLocalTotalTimeList, resData.advertisementList)) {
console.log('相等')
localAdvertisementList.filter((item: any) => {
if (item.targetUrl.indexOf(history.location.pathname) > -1) {
if (item.firstTotalTimes > 0) {
setAdItem(item)
}
}
})
} else {
console.log('不相等')
const cloneList = JSON.parse(JSON.stringify(resData.advertisementList));
for (let i = 0; i < cloneList.length; i++) {
for (let j = 0; j < cloneLocalAdvertisementList.length; j++) {
if (_.isEqual(cloneList[i].pkId, cloneLocalAdvertisementList[j].pkId)) {
if (_.isEqual(cloneList[i], cloneLocalAdvertisementList[j])) {
cloneList[i].firstTotalTimes = localAdvertisementList[j].firstTotalTimes
} else {
cloneList[i].firstTotalTimes = cloneList[i].totalTimes
}
}
}
}
localStorage.advertisementList = JSON.stringify(cloneList);
cloneList.filter((item: any) => {
if (item.targetUrl.indexOf(history.location.pathname) > -1) {
if (item.firstTotalTimes > 0) {
setAdItem(item)
setIsShow(true)
}
}
})
}
}
})
}, [])
難點
JS 的資料可變性
第一個坑點在 JS 的資料是可變的,所以要對其資料進行深拷貝,才不會影響到其他資料,這裡我用了最簡單的深拷貝:JSON.parse(JSON.stringify)
const cloneLocalAdvertisementList = JSON.parse(
JSON.stringify(localAdvertisementList),
)
判斷後臺那個資料修改
在之前表述中已經表明,當本地儲存和請求過來的資料不一致時要判斷,哪要做重置,哪些頁面則維持原狀。這就要對兩個陣列進行對比,最簡單的方法就是做雙迴圈(On2).
先 const cloneList = JSON.parse(JSON.stringify(resData.advertisementList));
,深拷貝後臺返回資料,這樣對 cloneList 進行處理時就不會影響到原資料。cloneLocalAdvertisementList
則是本地的儲存
if (_.isEqual(cloneList[i].pkId, cloneLocalAdvertisementList[j].pkId))
,pkId 是廣告唯一標識,先識別陣列中的每一個物件,這是一一對應的,再判斷 if (_.isEqual(cloneList[i], cloneLocalAdvertisementList[j]))
,對比物件中的值,如果是 true,即完全相等,說明後臺資料沒有變化,那就將本地儲存中的 firstTotalTimes 賦值給 cloneList 上的 firstTotalTimes 。如果是 false,說明後臺已經修改,就把 firstTotalTimes 重置為本次拉取資料中的 totalTimes
const localAdvertisementList = JSON.parse(localStorage.advertisementList)
const cloneLocalAdvertisementList = JSON.parse(JSON.stringify(localAdvertisementList))
...
const cloneList = JSON.parse(JSON.stringify(resData.advertisementList));
for (let i = 0; i < cloneList.length; i++) {
for (let j = 0; j < cloneLocalAdvertisementList.length; j++) {
if (_.isEqual(cloneList[i].pkId, cloneLocalAdvertisementList[j].pkId)) {
if (_.isEqual(cloneList[i], cloneLocalAdvertisementList[j])) {
cloneList[i].firstTotalTimes = localAdvertisementList[j].firstTotalTimes
} else {
cloneList[i].firstTotalTimes = cloneList[i].totalTimes
}
}
}
}
以上,就是對這次專案的核心程式碼,當然,還要考慮到 App 端開啟和 微信開啟的差異,以及當未登入狀態下的去登入後資料的更新等等,但這些可以通過監聽登入來判斷(useEffect 依賴資料)實現
總結
這次被資料可變性坑了,通過 debugger 來排查
雙迴圈在實際專案中用的次數不多,所以對此做記錄