前言
搜索列表页是大家经常接触的页面,如果做好一个列表页其实是不容易的,因为细节的地方很多;最近做了很多列表搜索页面,关于页面有倒计时并且刷新状态的需求做了不少,总结出一点经验,与大家分享,希望能对大家有所帮助。
关于首屏渲染
同步接口渲染页面
- 方案一:页面是接口同步请求,信息合并,渲染页面。即从A接口获取一个基本信息list,然后依赖A接口的数据,再请求B接口,B接口返回的数据和A接口的数据进行融合,渲染页面;
方案一逻辑:
1 | //list是融合接口A和接口B之后的数据 |
- 方案二:从A接口获取基本信息list后,直接用A接口的list渲染页面,同时拿A接口数据去请求B接口,等B接口数据回来后,将B接口的数据更新渲染页面;
方案二的逻辑如下:
1 | //list是基本信息;objCurr是依赖基本信息获取到的实时信息 |
比较方案一和方案二,不难发现,方案二的处理方式更好,因为提高了首屏的渲染速度,在A基本信息接口数据回来后就直接渲染页面,不再等待B接口回来一起渲染;减少了B接口的等待时间,页面上依赖B接口的数据可以先给默认值或者为空;这样加载页面的时候展示给用户的就是有基本的信息,一些实时信息等B接口返回后再渲染。
倒计时开启
- 常规操作倒计时,即在Good组件上开启:这样页面上每个卡片都有自己的倒计时,互不干扰,相互独立;
- 缺点:页面卡片很多时,会开启很多倒计时,影响性能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47// Good.tsx中
function Countdown(props: Props) {
let cd: any = null;
const startTimeStr = forMatTime(props.startTime)
const [strTime, setSecond] = useState(startTimeStr);
const { auctionStatus} = props;
let time = props.remainTime;
const tick = () => {
time = time - 1000;
if (time > -1000){
if (auctionStatus === 1){
const strTimeMe = forMatRemainTime(time)
setSecond(strTimeMe)
}
}else{
clearInterval(cd);
cd = null
props.refresh()
}
};
useEffect(() => {
clearInterval(cd);
cd = null
const { remainTime} = props;
if(remainTime > 0){
cd = setInterval(tick, 1000);
}else{
if(auctionStatus < 2){
setTimeout(()=>{props.refresh()},600)
}else{
setSecond(forMatTime(props.endTime))
}
}
return () => {clearInterval(cd);cd = null}
}, [props.remainTime]);
return (
<div className="broke-countdown">
{props.auctionStatus === 1 ? (<span style={{ paddingRight: 5 }}>距离结束预计:</span>) : (props.auctionStatus === 0 ? (<span style={{ paddingRight: 5 }}>开始时间:</span>) : (<span style={{ paddingRight: 5 }}>结束时间:</span>))}
<em>
{strTime}
</em>
</div>
);
}
- 在获取到数据之后,开启一个倒计时:
- 优点:页面只开启一个倒计时,大大节约性能;
- 缺点;如果页面数据很多,每一秒轮循一次列表可能造成倒计时设置一秒但是实际时间大于1秒,且数据很多的时候也会有性能问题;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27const timerW = setInterval(() => {
let Ids:any[] = [];
listRef.current.forEach((item: any) => {
if (item.auctionStatus === 1 || item.auctionStatus === 0) {
item.remainTime -= 1000
if (item.remainTime <= 0) {
Ids.push(item.paimaiId)
}
}
})
if (Ids.length !== 0){
getGoodStatus(Ids).then((result: any) => {
if(result && result.length > 0) {
result.map((item:any) => {
listRef.current = listRef.current.map((_:any)=> {
if(_.id === item.id) {
_ = Object.assign({}, _, item)
return _
}
return _
})
})
}
})
}
setList([...listRef.current])
}, 1000)
获取当前屏
在M端列表数据很长的时候,不管是单独开启倒计时还是页面只开启一个倒计时,都会有性能问题,所以当数据超过1000条时,一般我们需要获取当前屏幕数据做处理,而不是操作整个列表数据。
获取当前屏幕的优点显而易见,它大大减少了数据操作的长度,但是也有缺点,因为你需要不断的去获取当前屏幕的dom,或者说视窗窗口的index下标,这也额外的增加了不必要的非业务逻辑;所以我们建议当已知列表的长度可能超过1000条数据时,再开启获取当前屏方法;
- 卡片高度固定:如果列表的每个卡片高度固定,直接计算的list的父盒子top值除以单个卡片高度;计算出当前屏幕内的index;此方法简单,性能好;缺点是一旦需求卡片高度不固定,就不可以使用;
1 | //获取视窗窗口起始index |
- 卡片高度不固定:如果卡片高度不固定,可以进行代码修改,并不复杂;即修改代码中WINDOW_ITEM_INDEX值即可;(逻辑:获取当前list的父盒子top值,按照卡片可能的最小及最大高度,分别计算出可能在屏幕内的minIndex及maxIndex;截取总list的minIndex和maxIndex获得lessList,滚动事件里面不断的for循环lessList,比较lessList中每一项—–需要提前给每一个dom添加上唯一id,使用id获取dom,并计算dom是否在窗口内——-得到窗口内的dom下标i,最终的WINDOW_ITEM_INDEX = i + minIndex;即得到当前屏幕的index)
- 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//在滚动函数中执行
const content = document.querySelector('.index__main')
let minIndex = 0;
let maxIndex = allPageList.length -1;
let WINDOW_ITEM_INDEX = 0;
let lessList = allPageList
if (content) {
const { top } = content.getBoundingClientRect()
minIndex = Math.floor(top/ITEM_HEIGHT_Max)
maxIndex = Math.floor(top/ITEM_HEIGHT_Min)
lessList = allPageList.slice(minIndex, maxIndex)
for (var i = 0; i < this.lessList.length; i++) {
let id = i + minIndex + '_' + lessList[i].paimaiId
let elem = document.getElementById(id);
if (elem) {
var bottom = elem.getBoundingClientRect().bottom;
var clientHeight = (document.documentElement || document.body).clientHeight;
if (bottom > 0 && bottom < clientHeight) {
WINDOW_ITEM_INDEX = i + minIndex
break;
}
}
}
}
根据视窗窗口优化列表操作
获取到当前屏幕是index之后,关于倒计时我们可以只开启当前屏幕的,屏幕之外的倒计时就不再开启,在页面滚动的时候不断的更新要开启的倒计时list即可
需要注意的是,当屏幕之外的数据停止倒计时后,再开启的时候,倒计时就不能用之前的剩余时间,需要重新获取数据,拿到当前剩余时间再开启倒计时;所以当页面滚动的时候不仅需要不断的更新当前那段list需要开启倒计时,同时需要不断的请求新的数据以更新倒计时时间。
1 | this.timer = setInterval(() => { |
数据缓存
最后说到列表就不能不提起数据缓存,因为列表在点击进入详情的时候,返回都要定位到之前的位置,并且当页面滚动很多的时候不可能把数据全部重新加载一遍,所以就需要缓存。
本文章不建议列表使用localStory缓存数据,代替使用session。
点击卡片,需要记住当前的top值及当前列表数据
1
2
3
4
5
6
7
8//
const content = document.querySelector('.index__main')
if (content) {
const { top } = content.getBoundingClientRect()
window.sessionStorage.setItem('pageTop', JSON.stringify(top));
window.location.href = url
}
window.sessionStorage.setItem('list', JSON.stringify(this.list));当页面返回有缓存的时候,使用缓存
// let mainkey = sessionStorage.getItem('list') if(window.sessionStorage && mainkey){ let top = Number(sessionStorage.getItem('pageTop')); let ListDataStr = sessionStorage.getItem(mainkey); let ListData = JSON.parse(ListDataStr); this.setState({ list: ListData, isLoading: false },()=> {this.toJump(top)}) } //使用完缓存之后,将缓存清除 toJump = (top: number): void => { let scrollElement = document.querySelector('#app') // 对应id的滚动容器 if(scrollElement) { scrollElement.scrollTop = -top sessionStorage.removeItem('list'); sessionStorage.removeItem('pageTop'); // sessionStorage.clear(); } }