偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

瀑布流組件陷入商品重復(fù)怪圈?我是如何用心一解的!

開(kāi)發(fā) 前端
這個(gè)方法不僅適用于當(dāng)前場(chǎng)景,我們很多的業(yè)務(wù)場(chǎng)景都會(huì)遇到這種情況,會(huì)被動(dòng)接受多個(gè)請(qǐng)求,但是這些請(qǐng)求還要有序的執(zhí)行,我們都可以使用這種方法。

背景

某天我們公司小程序收到線上反饋,在商品列表頁(yè)面為什么我劃著劃著劃著,就會(huì)出現(xiàn)一些重復(fù)商品......

圖片

在講這個(gè)問(wèn)題之前,先講一下我們是如何實(shí)現(xiàn)瀑布流組件的

瀑布流組件

什么是瀑布流組件

如圖所示下方商品列表就采用了瀑布流的布局,視覺(jué)表現(xiàn)為參差不齊的多欄布局。

圖片

如何實(shí)現(xiàn)一個(gè)瀑布流組件

下面簡(jiǎn)單寫(xiě)一下實(shí)現(xiàn)瀑布流的思路,左右兩列布局,根據(jù)每一列的高度來(lái)判斷下次插入到哪一列中,每次插入列中需重新計(jì)算高度,將下一個(gè)節(jié)點(diǎn)插入短的哪一列中,如下圖所示:

圖片

圖片

下面代碼示例(僅展示思路)

// dataList 就是我們整個(gè)的商品卡片列表的數(shù)據(jù) ,用戶滑動(dòng)到底部會(huì)加載新一頁(yè)的數(shù)據(jù) 會(huì)再次觸發(fā) watch
watch(() => props.dataList ,(newList) => {
  dataRender(newList)
},{
  immediate: true,
})

const dataRender = async (newList) => {
  // 獲取左右兩邊的高度
  let leftHeight: number = await getViewHeight('#left')
  let rightHeight: number = await getViewHeight('#right')
  // 取下一頁(yè)數(shù)據(jù)
  const tempList = newVal.slice(lastIndex.value, newVal.length)
  for await (const item of tempList) {
    leftHeight <= rightHeight ? leftDataList.value.push(item) : rightDataList.value.push(item); //判斷兩邊高度,來(lái)決定添加到那邊
    // 渲染dom
    await nextTick();
    // 獲取dom渲染后的 左右兩邊的高度
    leftHeight = await getViewHeight('#left')
    rightHeight = await getViewHeight('#right')
  }
  lastIndex.value = newList.length
}
<template>
  <view>
    <view id="left">xxxx</view>
    <view id="right">xxxx</view>
  </view>
</template>

當(dāng)用戶滾動(dòng)到底部的時(shí)候會(huì)加載下一頁(yè)的數(shù)據(jù),dataList 會(huì)發(fā)生變化,組件會(huì)監(jiān)聽(tīng)到 dataList 的變化來(lái)執(zhí)行 dataRender,dataRender 中會(huì)去計(jì)算左右兩列的高度,哪邊更短來(lái)插入哪邊,循環(huán) list 來(lái)完成整個(gè)列表的插入。

商品重復(fù)的原因

乍一看上面代碼寫(xiě)的很完美,但是卻忽略 DOM 渲染還需要時(shí)間,代碼中使用了 for await 保證異步循環(huán)有序進(jìn)行,并且保證數(shù)據(jù)變化后 DOM 能渲染完成后獲取到新的列高,這樣卻導(dǎo)致了 DOM 渲染的比較慢。DOM 在沒(méi)有加載完成的情況下,用戶再次滑動(dòng)到底部會(huì)再次加載新的一頁(yè)數(shù)據(jù),導(dǎo)致 watch 又會(huì)被觸發(fā),dataRender 會(huì)再次被執(zhí)行,相當(dāng)于會(huì)存在多個(gè) dataRender 同時(shí)在執(zhí)行。但是 dataRender 中使用到了全局的 leftDataList、rightDataList 和 lastIndex ,如果多個(gè) dataRender 同時(shí)執(zhí)行的話就會(huì)到數(shù)據(jù)錯(cuò)亂,lastIndex 錯(cuò)亂會(huì)導(dǎo)致商品重復(fù),leftDataList 和 rightDataList 錯(cuò)亂會(huì)導(dǎo)致順序問(wèn)題。

下面用偽代碼講述一下之間的關(guān)系

// 正常情況代碼會(huì)像如下情況去走
list = [1,2,3,4,5]
// 數(shù)組執(zhí)行完成后
lastIndex = 5
// 加載下一頁(yè)數(shù)據(jù)后 
list = [1,2,3,4,5,6,7,8,9,10]

list.slice(lastIndex, list.length) // [6,7,8,9,10]

但是如果 dataRender 同時(shí)執(zhí)行 大家都共用同一個(gè) lastIndex ,lastIndex 并不是最新的,就會(huì)變成下面這種情況

list.slice(lastIndex, list.length) // [1,2,3,4,5,6,7,8,9,10]

同理順序錯(cuò)亂也是這種情況

解決方案

出現(xiàn)這個(gè)問(wèn)題的原因是存在多個(gè) dataRender 同時(shí)執(zhí)行,那我們只需想辦法在同一時(shí)間只能有一個(gè)在執(zhí)行就可以了。

方法一(復(fù)雜,不推薦):標(biāo)記位大法

看著這個(gè)方法相信大部分人經(jīng)常把它用作防抖節(jié)流,例如不想讓某個(gè)按鈕頻繁點(diǎn)擊導(dǎo)致發(fā)送過(guò)多的請(qǐng)求、點(diǎn)擊的時(shí)候讓某個(gè)請(qǐng)求完全返回結(jié)果后才能再次觸發(fā)下次請(qǐng)求等。因此我們這里的思路也是控制異步任務(wù)的次數(shù),在一個(gè) dataRender 完全執(zhí)行完成之后才能執(zhí)行另一個(gè) dataRender ,在這里我們首先添加一個(gè)全局標(biāo)記 fallLoad, 在最后一個(gè)節(jié)點(diǎn)渲染完才可以執(zhí)行 dataRender,代碼改造如下

const fallLoad = ref(true)

watch(() => {
  if(fallLoad.value) {
    dataRender()
    fallLoad.value = false
  }
})

const dataRender = async () => {
  let i = 0
  
  const tempList = newVal.slice(lastIndex.value, newVal.length)

  for await (const item of tempList) {
    i++
    leftHeight <= rightHeight ? leftDataList.value.push(item) : rightDataList.value.push(item); //判斷兩邊高度,來(lái)決定添加到那邊
    // 等待dom渲染完成
    await nextTick();
    // 獲取dom渲染后的 左右兩邊的高度
    leftHeight = await getViewHeight('#left')
    rightHeight = await getViewHeight('#right')
    // 判斷是最后一個(gè)節(jié)點(diǎn)
    if((tempList.length - 1) === i) {
      fallLoad.value = true
    }
  }
  lastIndex.value = newList.length
}

這樣的話會(huì)丟棄掉用戶快速滑動(dòng)時(shí)觸發(fā)的 dataRender ,只有在 DOM 渲染完成后再次觸發(fā)新的請(qǐng)求時(shí)才會(huì)再次觸發(fā)。但是這樣可能會(huì)存在另外一個(gè)問(wèn)題,有部分的 dataRender 被丟棄掉了,同時(shí)用戶把所有的數(shù)據(jù)都加載完成了,沒(méi)有新的數(shù)據(jù)來(lái)觸發(fā) watch ,這就導(dǎo)致部分商品的數(shù)據(jù)準(zhǔn)備好了但在頁(yè)面上沒(méi)有渲染,因此我們還需要針對(duì)這種情況再去做單獨(dú)處理, ,我們可以額外加一個(gè)狀態(tài)來(lái)判斷 rightDataList + leftDataList 的總數(shù)是否等于 dataList,不等于的時(shí)候可以再觸發(fā)一次 dataRender ......

其實(shí)我們這種場(chǎng)景其實(shí)已經(jīng)不太適合用標(biāo)記位大法,強(qiáng)行使用只會(huì)讓代碼變成一座“屎山”,但是其實(shí)在我們?nèi)粘I(yè)務(wù)中,添加標(biāo)記位是一種很實(shí)用的方法,比如給某個(gè)按鈕添加 loading ,防止某些事件、請(qǐng)求頻繁執(zhí)行等。

方法二(優(yōu)雅,推薦):Promise + 隊(duì)列 大法

由于我們并不能丟棄異常情況觸發(fā)的 dataRender, 那我們只能讓 dataRender 有序的執(zhí)行。

我們重新整理思路,首先我們先把復(fù)雜的問(wèn)題簡(jiǎn)單化。拋開(kāi)我們的業(yè)務(wù)場(chǎng)景,dataRender 就可以當(dāng)做一個(gè)異步的請(qǐng)求,然后問(wèn)題就變成了在同一時(shí)間我們收到了多個(gè)異步的請(qǐng)求,我們?cè)趺醋屵@些異步請(qǐng)求自動(dòng)、有序執(zhí)行。

經(jīng)過(guò)上面的推導(dǎo)我們拆解出以下幾個(gè)關(guān)鍵點(diǎn):

  1. 我們需要一個(gè)隊(duì)列,隊(duì)列中存儲(chǔ)每個(gè)異步任務(wù)
  2. 當(dāng)把這個(gè)任務(wù)添加到這個(gè)隊(duì)列中的時(shí)候自動(dòng)執(zhí)行第一個(gè)任務(wù)
  3. 我們需要使用 promise.then() 來(lái)保證任務(wù)有序的執(zhí)行
  4. 當(dāng)存隊(duì)列中在多個(gè)異步任務(wù)的時(shí)候,怎么在執(zhí)行完成第一個(gè)之后再去自動(dòng)的執(zhí)行后續(xù)的任務(wù)

第一次執(zhí)行的時(shí)機(jī)其實(shí)我們是知道,那我們需要現(xiàn)在解決的問(wèn)題是執(zhí)行完成第一個(gè)后怎么去自動(dòng)執(zhí)行后續(xù)的請(qǐng)求?

圖片

  1. 使用循環(huán),可參考瀑布流組件中的 for await of 確保每次異步任務(wù)的執(zhí)行,這里就不過(guò)多闡述了,這么寫(xiě)代碼不太優(yōu)雅
  2. 使用遞歸,在每個(gè) promise.then 中遞歸下一個(gè) promise

通過(guò)這幾點(diǎn)關(guān)鍵點(diǎn)我們寫(xiě)出使用遞歸的方案的代碼:

class asyncQueue {
  constructor() {
    this.asyncList = [];
    this.inProgress = false;
  }

  add(asyncFunc) {
    return new Promise((resolve, reject) => {
      this.asyncList.push({asyncFunc, resolve, reject});
      if (!this.inProgress) {
        this.execute();
      }
    });
  }

  execute() {
    if (this.asyncList.length > 0) {
      const currentAsyncTask = this.asyncList.shift();

      currentAsyncTask.asyncFunc()
        .then(result => {
          currentAsyncTask.resolve(result);
          this.execute();
        })
        .catch(error => {
          currentAsyncTask.reject(error);
          this.execute();
        });

      this.inProgress = true;
    } else {
      this.inProgress = false;
    }
  }
}

export default asyncQueue

每次調(diào)用 add 方法會(huì)往隊(duì)列中添加經(jīng)過(guò)特殊包裝過(guò)的異步任務(wù),并且只有在只有在沒(méi)有正在執(zhí)行中的任務(wù)的時(shí)候才開(kāi)始執(zhí)行 execute 方法。在每次執(zhí)行異步任務(wù)時(shí)會(huì)從隊(duì)列中 shift ,利用 promise.then 并且遞歸調(diào)用該方法,實(shí)現(xiàn)有序并且自動(dòng)執(zhí)行任務(wù)。在封裝在這方法的過(guò)程中同樣也使用到了我們的標(biāo)記位大法 inProgress ,來(lái)保證我們正在執(zhí)行當(dāng)前隊(duì)列時(shí),突然又進(jìn)來(lái)新的任務(wù)而導(dǎo)致隊(duì)列執(zhí)行錯(cuò)亂。

調(diào)用方法如下:

const queue = new asyncQueue()

watch(() => props.dataList, async (newVal, oldVal) => {
  queue.add(() => dataRender(newVal))
}, {
  immediate: true,
  deep: true
})

通過(guò)上述代碼我們就可以,讓我們的每一個(gè)異步任務(wù)有順序的執(zhí)行,并且讓每一個(gè)異步任務(wù)執(zhí)行完成以后自動(dòng)執(zhí)行下一個(gè),完美的達(dá)到了我的需求。

其實(shí)這個(gè)方法不僅適用于當(dāng)前場(chǎng)景,我們很多的業(yè)務(wù)場(chǎng)景都會(huì)遇到這種情況,會(huì)被動(dòng)接受多個(gè)請(qǐng)求,但是這些請(qǐng)求還要有序的執(zhí)行,我們都可以使用這種方法。

下面我簡(jiǎn)單列舉了兩種其他的場(chǎng)景:

  1. 比如某個(gè)按鈕用戶點(diǎn)擊了多次,但是我們要讓這些請(qǐng)求有序的執(zhí)行并且依次拿到這些請(qǐng)求返回的數(shù)據(jù)
  2. 某些高頻的通信操作,我們不能丟棄用戶的每次通信,而是需要用這種隊(duì)列的方式,自動(dòng)、有序的執(zhí)行

總結(jié)

上述的這些“點(diǎn)” ,標(biāo)記位、promise、隊(duì)列、遞歸等,在日常開(kāi)發(fā)中幾乎充斥在我們項(xiàng)目的每一個(gè)角落,但是如何使用好這些”點(diǎn)“值得我們深思的。

責(zé)任編輯:武曉燕 來(lái)源: 政采云技術(shù)
相關(guān)推薦

2010-12-14 11:42:45

職場(chǎng)

2022-04-14 15:53:12

開(kāi)發(fā)瀑布流組件

2018-04-03 10:24:13

2017-05-02 13:38:51

CSS繪制形狀

2017-04-11 17:22:57

編程程序員語(yǔ)言

2021-03-22 11:10:09

Redis架構(gòu)MQ

2015-04-14 09:31:10

AWSAWS PaaSSaaS可視化編排

2018-08-10 14:57:03

UnixMySQL命令

2015-02-26 18:09:29

WaterFall V瀑布流Dynamic Gri

2017-05-02 20:56:36

機(jī)器學(xué)習(xí)HR簡(jiǎn)歷

2024-09-03 17:04:15

前端算法布局

2022-02-20 19:02:16

RollupVue 2JavaScrip

2012-05-02 13:53:00

JavaScript

2023-03-21 17:06:24

樹(shù)莓派路由器

2013-07-01 14:41:46

失敗移動(dòng)APP移動(dòng)創(chuàng)業(yè)

2020-01-13 14:39:06

FlinkSQL無(wú)限流

2016-09-26 15:14:28

Javascript前端vue

2021-11-29 22:39:39

引擎Flink架構(gòu)

2015-08-10 14:56:31

Google

2020-04-21 08:30:32

AI人工智能語(yǔ)言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)