專案實戰:彈出廣告任意頁面展示

山頭人漢波發表於2022-04-26

最近接到一個需求,產品經理希望能新增彈窗廣告,廣告可根據後臺配置在應用任意頁面彈出展示。當後臺改變當前頁面廣告次數、連結或者目標頁後,當前頁面資料修改,不影響其他頁面資料

例如後臺設定“首頁”出現廣告 1 次,“我的”頁面廣告出現 3 次,使用者進去後關閉了“首頁”廣告 1 次,關閉了“我的”頁面廣告 2 次。此時退出應用,後臺將“首頁”廣告設定為 2 次,那麼該使用者“首頁”廣告重置為 2 次,“我的”頁面廣告仍為 1 次( 3 - 2)

需求分析

後端返回的資料必然是個陣列,每個物件中會有目標頁(展示的頁面),跳轉連結總出現的次數三引數。前端要對資料進行處理:

  • 當本地沒有資料時(第一次進入),將總出現次數賦值給一引數 firstTotalTimes(記錄原總出現次數)
  • 當本地有資料(非第一次進入)

    1. 將本地儲存中的 firstTotalTimes 清除,返回值賦值為 removeLocalTotalTimeList
    2. 將 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 來排查

雙迴圈在實際專案中用的次數不多,所以對此做記錄

相關文章