vue 工具函数 useInfiniteScroll实现数据懒加载
一、问题
1. 大量数据在前端一次性渲染会出现 页面卡顿甚至失去响应,影响用户体验。
2. 为了解决这个问题:一般有两种思路
思路一:只渲染少量数据——分页显示、下拉搜索更新数据
思路二:渲染可视区域内的数据——滚动到可视区域底部再加载(懒加载)
二、useInfiniteScroll滚动懒加载
1.具体思路
· 1)一开始获取到所有的数据searchedControlSales
2)渲染指定数量的数据 showOptions
3)useInfiniteScroll滚动到 渲染区域的底部时,再追加要 渲染的数据
注:需要判断什么时候不再继续追加数据,数据加载完成时,避免不必要的追加行为。(判断方法:已渲染的数据量===总数据量)
2.原始代码
<template><div><div class="h-8 flex items-center"><span class="text-[#4d4d4d]">当前已选:</span><span class="font-semibold text-[1a1a1a]">{{ currentControlSale }}</span></div><div class="check-group-container custom-scroll-bar"><AFormItemRest><AInputSearchstyle="width: 200px"placeholder="请输入搜索关键字"class="mb-4":disabled="disabled"@change="searchControl"/></AFormItemRest><ARadioGroup v-model:value="bindValue" :disabled="disabled" class="!block"><div class="radios"><div v-for="item of searchedControlSales" :key="item.storeGroupId" class="flex items-center"><ARadio :value="item.storeGroupId" style="margin-right: 0">{{ item.groupName }}</ARadio><ATooltip v-if="item.groupType === 5" :title="item.groupDesc"><SvgIcon name="右箭头-中" class="cursor-pointer" :size="8.5" /></ATooltip><SvgIconv-elsename="右箭头-中"class="cursor-pointer":size="8.5"@click="viewControl(item.storeGroupId)"/></div></div></ARadioGroup></div></div>
</template><script lang="ts" setup>
import { getRestrictGroups, getStoreGroupList } from '@/api/commodity-management/goodsOnShelves'
import { RestrictGroup } from '@/api/commodity-management/model/groupEditModel'
import { iframeModal } from '@/components/iframeModal'
import { useVModel } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
import { WholesaleType } from '@/enum/common'interface Props {value?: numberoptions?: RestrictGroup[]disabled?: booleanwholesaleType?: WholesaleType
}const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const bindValue = useVModel(props, 'value', emit)const controlSales = ref<RestrictGroup[]>([])
const searchedControlSales = ref<RestrictGroup[]>([])watch(() => props.options,() => {setControlSales()}
)setControlSales()
async function setControlSales() {if (props.options) {controlSales.value = props.options} else {try {controlSales.value = await getStoreGroupList({ wholesaleType: props.wholesaleType })} catch (error) {console.error(error)}}searchedControlSales.value = cloneDeep(controlSales.value)
}function viewControl(storeGroupId: number) {iframeModal({url: `/provider.php/Admin/SaleControl/groupStore?store_group_id=${storeGroupId}`,fullScreen: true,title: '查看控销药店'})
}function searchControl(e: Event) {if (e.target instanceof HTMLInputElement) {const keyWord = e.target.valueif (!keyWord) {searchedControlSales.value = cloneDeep(controlSales.value)}searchedControlSales.value = controlSales.value.filter((item) => item.groupName.includes(keyWord))}
}
const currentControlSale = computed(() => {return controlSales.value.find((item) => item.storeGroupId === bindValue.value)?.groupName || ''
})
</script><style lang="less" scoped>
.check-group-container {padding: 16px;margin-top: 5px;max-width: 593px;max-height: 168px;overflow-y: auto;background: #fafafa;border-radius: 4px;.radios {flex-wrap: wrap;display: flex;gap: 16px 20px;}
}
</style>
3.useInfiniteScroll 懒加载后的代码
<template><div><div class="h-8 flex items-center"><span class="text-[#4d4d4d]">当前已选:</span><span class="font-semibold text-[1a1a1a]">{{ currentControlSale }}</span></div><div ref="radioGroup" class="check-group-container custom-scroll-bar"><AFormItemRest><AInputSearchstyle="width: 200px"placeholder="请输入搜索关键字"class="mb-4":disabled="disabled"@change="searchControl"/></AFormItemRest><ARadioGroup v-model:value="bindValue" :disabled="disabled" class="!block"><div class="radios"><div v-for="item of showOptions" :key="item.storeGroupId" class="flex items-center"><ARadio :value="item.storeGroupId" style="margin-right: 0">{{ item.groupName }}</ARadio><ATooltip v-if="item.groupType === 5" :title="item.groupDesc"><SvgIcon name="右箭头-中" class="cursor-pointer" :size="8.5" /></ATooltip><SvgIconv-elsename="右箭头-中"class="cursor-pointer":size="8.5"@click="viewControl(item.storeGroupId)"/></div></div></ARadioGroup></div></div>
</template><script lang="ts" setup>
import { getStoreGroupList } from '@/api/commodity-management/goodsOnShelves'
import type { RestrictGroup } from '@/api/commodity-management/model/groupEditModel'
import { iframeModal } from '@/components/iframeModal'
import { useInfiniteScroll, useVModel } from '@vueuse/core'
import { cloneDeep } from 'lodash-es'
import type { WholesaleType } from '@/enum/common'interface Props {value?: numberoptions?: RestrictGroup[]disabled?: booleanwholesaleType?: WholesaleType
}const props = defineProps<Props>()
const emit = defineEmits(['update:value'])
const bindValue = useVModel(props, 'value', emit)const controlSales = ref<RestrictGroup[]>([])
const searchedControlSales = ref<RestrictGroup[]>([])
const radioGroup = ref()
const showOptions = ref<RestrictGroup[]>([])
const count = 100
function getShowOptions() {showOptions.value = searchedControlSales.value.slice(0, count)
}
watch(() => props.options,() => {setControlSales()}
)setControlSales()
async function setControlSales() {if (props.options) {controlSales.value = props.options} else {try {controlSales.value = await getStoreGroupList({ wholesaleType: props.wholesaleType })} catch (error) {console.error(error)}}searchedControlSales.value = cloneDeep(controlSales.value)getShowOptions()
}function viewControl(storeGroupId: number) {iframeModal({url: `/provider.php/Admin/SaleControl/groupStore?store_group_id=${storeGroupId}`,fullScreen: true,title: '查看控销药店'})
}function searchControl(e: Event) {if (e.target instanceof HTMLInputElement) {const keyWord = e.target.valueif (!keyWord) {searchedControlSales.value = cloneDeep(controlSales.value)}searchedControlSales.value = controlSales.value.filter((item) => item.groupName.includes(keyWord))getShowOptions()}
}
const currentControlSale = computed(() => {return controlSales.value.find((item) => item.storeGroupId === bindValue.value)?.groupName || ''
})useInfiniteScroll(radioGroup, () => {if (showOptions.value.length >= searchedControlSales.value.length) returnshowOptions.value.push(...searchedControlSales.value.slice(showOptions.value.length, showOptions.value.length + count))
})
</script><style lang="less" scoped>
.check-group-container {padding: 16px;margin-top: 5px;max-width: 593px;max-height: 168px;overflow-y: auto;background: #fafafa;border-radius: 4px;.radios {flex-wrap: wrap;display: flex;gap: 16px 20px;}
}
</style>
4.对比
5.另外的思路
1)后端给分页接口,返回数据总数
2) 在useInfiniteScroll回调中记录 下一页的页码,每次追加数据 都直接调接口获取。
优势:初始化时,不需要等待较长时间,且不需要占用 很大的内存空间存储数据、自己过滤数据。
三、总结
1. 使用 vue中的useInfiniteScroll可以方便的实现 滚动到 容器底部时 追加渲染数据。避免自己操作dom
/*
希望对你有帮助!
如有错误,欢迎指正!
*/