下面代码示例(仅展示思路)
// 堆代码 duidaima.com // dataList 就是我们整个的商品卡片列表的数据 ,用户滑动到底部会加载新一页的数据 会再次触发 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') // 取下一页数据 const tempList = newVal.slice(lastIndex.value, newVal.length) for await (const item of tempList) { leftHeight <= rightHeight ? leftDataList.value.push(item) : rightDataList.value.push(item); //判断两边高度,来决定添加到那边 // 渲染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>当用户滚动到底部的时候会加载下一页的数据,dataList 会发生变化,组件会监听到 dataList 的变化来执行 dataRender,dataRender 中会去计算左右两列的高度,哪边更短来插入哪边,循环 list 来完成整个列表的插入。
// 正常情况代码会像如下情况去走 list = [1,2,3,4,5] // 数组执行完成后 lastIndex = 5 // 加载下一页数据后 list = [1,2,3,4,5,6,7,8,9,10] list.slice(lastIndex, list.length) // [6,7,8,9,10]但是如果 dataRender 同时执行 大家都共用同一个 lastIndex ,lastIndex 并不是最新的,就会变成下面这种情况
list.slice(lastIndex, list.length) // [1,2,3,4,5,6,7,8,9,10]同理顺序错乱也是这种情况
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); //判断两边高度,来决定添加到那边 // 等待dom渲染完成 await nextTick(); // 获取dom渲染后的 左右两边的高度 leftHeight = await getViewHeight('#left') rightHeight = await getViewHeight('#right') // 判断是最后一个节点 if((tempList.length - 1) === i) { fallLoad.value = true } } lastIndex.value = newList.length }这样的话会丢弃掉用户快速滑动时触发的 dataRender ,只有在 DOM 渲染完成后再次触发新的请求时才会再次触发。但是这样可能会存在另外一个问题,有部分的 dataRender 被丢弃掉了,同时用户把所有的数据都加载完成了,没有新的数据来触发 watch ,这就导致部分商品的数据准备好了但在页面上没有渲染,因此我们还需要针对这种情况再去做单独处理, ,我们可以额外加一个状态来判断 rightDataList + leftDataList 的总数是否等于 dataList,不等于的时候可以再触发一次 dataRender ......
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每次调用 add 方法会往队列中添加经过特殊包装过的异步任务,并且只有在只有在没有正在执行中的任务的时候才开始执行 execute 方法。在每次执行异步任务时会从队列中 shift ,利用 promise.then 并且递归调用该方法,实现有序并且自动执行任务。在封装在这方法的过程中同样也使用到了我们的标记位大法 inProgress ,来保证我们正在执行当前队列时,突然又进来新的任务而导致队列执行错乱。
const queue = new asyncQueue() watch(() => props.dataList, async (newVal, oldVal) => { queue.add(() => dataRender(newVal)) }, { immediate: true, deep: true })通过上述代码我们就可以,让我们的每一个异步任务有顺序的执行,并且让每一个异步任务执行完成以后自动执行下一个,完美的达到了我的需求。其实这个方法不仅适用于当前场景,我们很多的业务场景都会遇到这种情况,会被动接受多个请求,但是这些请求还要有序的执行,我们都可以使用这种方法。