react+dva实现页面上滑加载数据功能
场景: 原PC页面分页展示的数据,现在为了用户使用方便,支持手机端就能随时查看;也就是说把本来PC展示的内容按照移动端的要求移植到手机上。于是老后端为了提高开发效率、节约开发成本,直接把原PC端分页接口甩在了我的脸上(这种情况基本上会经常碰到)。很多情况下前后端 要做的功能是可以互相甩的,就看谁理由逼格高、语气态度硬。当然我是那种热爱和平的人,如果别人嗯嗯啊啊推辞不愿做而自己又能做的事就不会去争的面红耳赤,毕竟代码敲的越多自身的收益也就越大,一味的甩锅和逃避就会失去很多提升自己的机会。
言归正传,本次的需求也很简单只要前端将分页展示的每页条数改成很大值,就可以拿到所有后台返回的数据了;但这样一来前端会出现一个问题,如果后台返回的数据量少三五十条还好,如果是三五百条或者更多,全部展示的时候手机就会出现明显的卡顿,针对这个问题前端可定要做类似PC那种分页效果:默认展示几条,在浏览完默认条数往下滑动的时候依次展示剩下内容即可。
实现思路: 进入页面,调用接口拿到全部数据,默认展示n条(本例中展示5条);在页面初次加载完毕后获取展示区域的DOM,给目标区域添加一个scroll滚动事件,每次触发滚动时把滚动的高度、目标区域高度之和与内容的scrollHeight进行比较;当前者等于后者时,表示内容已经滑动到了底部,此时将展示的数据源加上下一页的数据,重复此步骤直至展示全部数据。
步骤代码:
- 拿到数据,按分页的思路处理数据:
async componentDidMount(){
const { dispatch } = this.props;
await dispatch({
type: 'dynamicLoading/fetchDataList',
payload: {pageSize: 999999}
});
const { currentSize } = this.state;
const { dynamicLoading } = this.props;
if(currentSize === 0) return;
let tempList = mapListHandleFunc(dynamicLoading.dataInfo, currentSize);
this.setState({displayList: tempList, dataList: dynamicLoading.dataInfo});
const domNode = this.contentRef.current;
if(domNode){
domNode.addEventListener('scroll', this.onScrollFunc);
}
}
mapListHandleFunc方法将总数据按条件的进行截取:
function mapListHandleFunc(listData=[],size=0){
let tempList = [];
for(let i=0;i<listData.length;i++){
if(i>=size){
break;
}
tempList.push(listData[i]);
}
return tempList;
}
- 上滑判断数据加载(核心):
onScrollFunc=()=> {
const domNode = this.contentRef.current;
const clientHeight = domNode.clientHeight;
const scrollHeight = domNode.scrollHeight;
const scrollTop = domNode.scrollTop;
const { currentSize, flag, dataList } = this.state;
if(scrollTop+clientHeight >= scrollHeight && !flag){
this.setState({scorollLoading: true});
if(currentSize >= dataList.length) {
this.timeoutFunc = setTimeout(() => {
this.setState({scorollLoading: false, flag: true});
}, 1000);
return
}
this.timeoutFunc = setTimeout(() => {
let tmepCurrentSize = currentSize + 5;
let tempDisplayList = mapListHandleFunc(dataList, tmepCurrentSize);
this.setState({ scorollLoading: false, currentSize: tmepCurrentSize, displayList:tempDisplayList});
}, 1000);
}
}
- 展示页面全部代码:
import React, { Component } from 'react';
import { connect } from 'dva';
import { ActivityIndicator} from 'antd-mobile';
import moment from 'moment';
import { routerRedux } from 'dva/router';
import styles from './index.less';
class DynamicPage extends Component {
contentRef = React.createRef();
state = {
scorollLoading: false,
currentSize: 5,
displayList: [],
flag: false,
dataList: []
}
async componentDidMount(){
const { dispatch } = this.props;
await dispatch({
type: 'dynamicLoading/fetchDataList',
payload: {}
});
const { currentSize } = this.state;
const { dynamicLoading } = this.props;
if(currentSize === 0) return;
let tempList = mapListHandleFunc(dynamicLoading.dataInfo, currentSize);
this.setState({displayList: tempList, dataList: dynamicLoading.dataInfo});
const domNode = this.contentRef.current;
if(domNode){
domNode.addEventListener('scroll', this.onScrollFunc);
}
}
componentWillUnmount(){
const domNode = this.contentRef.current;
domNode.removeEventListener('scroll', this.onScrollFunc);
clearTimeout(this.timeoutFunc);
}
onScrollFunc=()=> {
const domNode = this.contentRef.current;
const clientHeight = domNode.clientHeight;
const scrollHeight = domNode.scrollHeight;
const scrollTop = domNode.scrollTop;
const { currentSize, flag, dataList } = this.state;
if(scrollTop+clientHeight >= scrollHeight && !flag){
this.setState({scorollLoading: true});
if(currentSize >= dataList.length) {
this.timeoutFunc = setTimeout(() => {
this.setState({scorollLoading: false, flag: true});
}, 1000);
return
}
this.timeoutFunc = setTimeout(() => {
let tmepCurrentSize = currentSize + 5;
let tempDisplayList = mapListHandleFunc(dataList, tmepCurrentSize);
this.setState({ scorollLoading: false, currentSize: tmepCurrentSize, displayList:tempDisplayList});
}, 1000);
}
}
detailFunc=(id)=>{
const { dispatch } = this.props;
dispatch(routerRedux.push(`/detail?id=${id}`))
}
render(){
const { scorollLoading, displayList, flag } = this.state;
return (
<div className={styles.home}>
<div className={styles.header}>
<span className={styles.headerAnimation}> 滑动加载数据功能演示 </span>
</div>
<div className={styles.content} ref={ this.contentRef } >
{
displayList.map((item, i) => (
<div className={styles.listItem} key={ i }>
<div className={styles.itemTitle}>
<div className={styles.title}>
<span>{ item.title }</span>
<span className={styles["t-detail"]} onClick={()=>this.detailFunc(item.id)}>详情</span>
</div>
<p className={styles.time}>{moment(item.time).format('YYYY-MM-DD HH:mm:ss')}</p>
<div className={styles.border} />
</div>
<div className={styles.itemDetail}>
<div className={styles.detail}>
<div className={styles.label}>申请方</div>
<div className={styles.value}>{ item.purchers }</div>
</div>
<div className={styles.detail}>
<div className={styles.label}>申请类型</div>
<div className={styles.value}>{ item.type }</div>
</div>
<div className={styles.detail}>
<div className={styles.label}>地区</div>
<div className={styles.value}>{ item.area }</div>
</div>
</div>
</div>
))
}
{
flag&&<div className={styles.scrollToBottomClass}>————嘤嘤嘤~没有更多啦~————</div>
}
<ActivityIndicator
toast
animating={ scorollLoading }
/>
</div>
<div className={styles.footer}></div>
</div>
)
}
}
const mapStateToProps = ({dynamicLoading, loading})=>{
return {dynamicLoading, gloabalLoading: loading.global, dymLoading: loading.effects['dynamicLoading/fetchDataList']};
}
export default connect(mapStateToProps)(DynamicPage);
function mapListHandleFunc(listData=[],size=0){
let tempList = [];
for(let i=0;i<listData.length;i++){
if(i>=size){
break;
}
tempList.push(listData[i]);
}
return tempList;
}
最终展示效果如下:

The end~
|