Ant-design-mobile: ListView onEndReached不停调用

Created on 13 Nov 2016  ·  29Comments  ·  Source: ant-design/ant-design-mobile

本地环境

antd-mobile 版本:├─┬ [email protected]
操作系统及其版本:Ubuntu 16.04
浏览器及其版本:Chrome Versión 52.0.2743.116 (64-bit)

你做了什么?

引入 antd-mobile 的 ListView, 并上拉

你期待的结果是:
ListView onEndReached使用正常

实际上的结果:
onEndReached不停调用,无法停止

可重现的在线演示
no

Most helpful comment

这个onEndReached太坑了。。我理解的 :你们判断渲染的DOM长度和datasource里的长度一致时,调用onEndReached函数,是为了预加载一页的数据,但是现在 你预加载一页数据的时候,没有按照pageSize的大小进行渲染,然后就导致渲染的DOM长度和datasource里的长度同时增加,然后就出现了递归调用,而这个递归又没有出口 ,就会导致循环调用onEndReached函数。

All 29 comments

我也碰到这个问题,我的环境是有RefreshControl的时候 ,并且手动设置了ListView的高

Can anyone fix this quickly? It's urgent

ListView 在滑动到达 onEndReachedThreshold 指定的距离后,继续滑动,会通过 scroll 事件、每隔 scrollEventThrottle 设置的时间、就会触发一次 onEndReached 回调。

I am sorry for the previous mistakes, please see this commit.

ListView 每隔 scrollEventThrottle 设置的时间、就会触发onScroll回调。
在滑动到达 onEndReachedThreshold 指定的距离临界值时、会触发一次 onEndReached 调用(在 threshold 的距离内“来回滑动”、不会再触发调用,只有滑回到外边、再次滑动到达 onEndReachedThreshold 指定的距离临界值时会再次触发调用)所以,如果 onEndReachedThreshold 设置的比较小,很容易滑出去再滑进来、也就很容易触发“不必要的”多次 onEndReached 调用,容易引起重复 Ajax 请求问题。

所以在实际项目里:在 onEndReached 调用里,需要结合一次 Ajax 请求开始到结束的 loading 状态,自行做避免重复请求的代码控制,详细做法参考官方 demo。

这个onEndReached太坑了。。我理解的 :你们判断渲染的DOM长度和datasource里的长度一致时,调用onEndReached函数,是为了预加载一页的数据,但是现在 你预加载一页数据的时候,没有按照pageSize的大小进行渲染,然后就导致渲染的DOM长度和datasource里的长度同时增加,然后就出现了递归调用,而这个递归又没有出口 ,就会导致循环调用onEndReached函数。

@warmhug 貌似不能通过Ajax的请求开始和介乎的loading状态来判断吧?
因为ajax结束时loading状态为false,但是此时onEndReached方法可能仍然在执行(被循环调用),但这时loading状态已经为false

@jxintang 是的,我也是像你这样理解,并且遇到这样的问题。ajax结束时,loading状态变为false,onEndReached 还是会执行。 并且,refreshControl组合在一起用的时候,下拉刷新清空了列表,同样会触发onEndReached,但这时并不是“onEndReachedThreshold 指定的距离后,继续滑动”. 求解。

在onEndReached里面判断一下数据是否已经加载完全,如果没有加载完毕再把isLoading设置为true,就可以了。就是,在设置isLoading为true前加个条件,不要直接写

@warmhug
感谢回复!
我的场景可能有点不一样。
是这样的。我是列表是整合了refreshControl和上拉加载更多,用redux管理状态。 下拉刷新的时候,为了更新列表,会清空列表。数据未返回的时候,可以通过状态判断去阻止onEndReadched回调中接口请求的发起。但问题是,数据返回后,我把刷新状态设为“非刷新中”状态后,会在短暂的时间内,列表界面还没渲染出来,还是会发生onEndReached的回调,并且状态已经是“非刷新中”,所以会导致发起了数据请求。

@jxintang 你的问题是否也一样?

我也碰到了类型的问题,但是我是在下拉刷新的时候出现下拉刷新无法结束的情况,refreshing等于true的时候也无法结束下拉刷新,我们的列表是套用了ant mobile的ListView, RefreshControl组件。数据方面,数据官方demo的写死的数据,我这里通过异步请求的数据

@dancinglone 应该是这种情况。个人认为,即使不是fetch获取数据,onEndReached也不应该不停地被调用才对。需要用户自己做个loading标识显得有点生硬。
可否换成:onEndReached只触发一次,此时ListView滚动中,onEndReached不会触发多次。待ListView停止滚动后,onEndReached就又处于可触发的状态。

@leonardgithub 嗯,这个需要慢慢完善吧。我更着急现在问题怎样解决呢,有什么idea不,求指点。 我怀疑只是我没有理解清楚一些什么 。这个是比较常规的东西,如果有问题应该好多人会遇到啊。

数据返回后,我把刷新状态设为“非刷新中”状态后,会在短暂的时间内,列表界面还没渲染出来,还是会发生onEndReached的回调

@dancinglone 这个可能会出现,设置下scrollEventThrottle,或者 settimeout 试试。

@35860368 现在 demo 的数据虽然是写死,但已经用 settimeout 模拟 ajax 返回了、基本和真实场景一样。另外 RefreshControl 遇到的问题,建议做个能跑的 demo,放到 GitHub 上我一起看下,可以参考 https://github.com/ant-design/antd-mobile-samples/tree/master/web-webpack 这里做下。

@leonardgithub onEndReached 多次被调用 和 react-native ListView 表现一致,至于这样是否合理正确,也认为有待确认。

@warmhug
这是我的代码
onRefresh = () => {
this.initData = [];
this.setState({
refreshing: true,
});
const param = { pageIndex: 1, pageSize: 10, listTitile: '美食' };
this.props.dispatch({ type: 'list/fetchonRefresh', payload: param });
};
我的意思是settimeout 只是模拟ajax数据返回,但是在实际情况中,我这边的情况是,只有当有请求返回的时候才去执行 this.setState({refreshing: false, });
但是实际情况是但我请求返回是,我去执行 this.setState({refreshing: false, });的时候,RefreshControl 的refreshing的状态还是true,并没有修改成false

@warmhug 参考最新 commit ~ 仍然有不解的地方, 还请指教 !

场景: ListView 列表 -> 滑动到底部 -> 加载更多 -> 触发 onEndReached 关键代码:

onEndReached = (event) => {
    if (event && this.props.loadMore) {
      const {isLoadMore, loadMore} = this.props.loadMore;
      if (isLoadMore) {
        if (!this.state.isLoading) {
          this.setState({isLoading: true, loadMoreValue: '加载中...'});
          loadMore(this.onLoadMoreCallback.bind(this));
        }
      } else {
        this.setState({loadMoreValue: '加载完毕'});
      }
    }
  }

说明:

  • 判断条件有三个( 是否使用加载更多, 是否正在加载, 是否还有更多) ...
  • 在测试网络断开的情况下, 就出现了 onEndReached 被触发多次的问题 ~

网络请求关键代码 (dva -> fetch):

try {
  const result = yield call(FindService.requestCommodity, {page: tempPage});

  yield put({
    type   : 'saveFindList',
    payload: {
      list     : result.list,
      pageCount: result.pageCount,
      page     : tempPage,
      hasLoadMore
    }
  });
  yield callback(SUCCESS);
} catch (err) {
  yield callback(FAILED);
}

回调处理关键代码:

  onLoadMoreCallback(state) {
    const stateValue = state == 1 ? '加载中' : state == 2 ? '没有更多' : state == 3 ? '加载失败' : '加载更多';
    this.setState({isLoading: false, loadMoreValue: stateValue});
  }

问题:
网络异常, 直接抛Error , 走 callback , 导致 isLoading 为 true, 然后 onEndReached 自动被触发, 而且触发多次

期待结果:
请问如何让它只触发一次 ~

@xoptimal 网络异常 回调里,能把 isLoading、loadMore 这些都设为 false 吗?

@warmhug 每次请求结果, 都会判断是否还有加载更多, 以及设置isLoading 为false , 我目前的做法是为了避免多次请求, 在请求结果上, 做了个延迟的处理, 是失败的情况下, 需要过5秒才可重新请求, 但是这个用户体验感觉并不好 .... 后续的5秒用户只能等待 ~

尝试把每页的条数增大一些,请求的数据填充的页面高度少于设定容器的高度会自动触发onEndReached事件

请问这个问题解决了没

+1

@leonardgithub this is not a bug, it's Normal behavior.

Not a bug? But why so many users encounter this problem?

My issue was referred by other user, I am not the only user which encounter the problem, please check the 6th point on https://github.com/TerryBeanX2/Webpack-React-Router-Redux-ES6#几点心得

If such a problems exists, can the antd-mobile's ListView be used in the production environment?

使用以下代码似乎能解决不断请求的问题

//hide the toast in a short time delay,otherwise loading toast won't be shown if hidden immediately
hideLoading = (msg) => {
       setTimeout(()=>{ 
             Toast.hide();
             if (msg) {
                  Toast.info(msg, 1.5);
            }
        }, 1000);
    };
    setNoLoading = (msg) => {
        this.hideLoading(msg);
        setTimeout(()=>{
            this.setState({isLoading: false});
        },3000);
    };
    onReachEnd = (e) => {
        console.log('上拉',this.state.isLoading);
        if (this.state.isLoading) {
            return
        }
        if (!this.hasMore) {
            Toast.info('无更多数据', 1.5);
            return;
        }
        this.setState({isLoading: true});
        let last = this.recordData[this.recordData.length - 1];
        if (last && last.time) {
            Toast.loading('加载中', 10);
            Service.traceList('0', last.time).then((response) => {
                if (response.code == 1) {
                    this.setNoLoading();
                    this.hasMore = response.hasMore || false;
                    let newL = response.info || [];
                    this.recordData = this.recordData.concat(newL);
                    this.setState({
                        dataSource: this.state.dataSource.cloneWithRows(this.recordData)
                    });
                } else {
                    this.setNoLoading(response.msg);
                }
            }, (error) => {
                this.setNoLoading();
            });
        }
    };

is there anyone who solve this problem?

In [email protected] , you can use PullToRefersh and react-infinite or your custom infinite-scroll.
@paranoidjk will add them soon.


Irrelevant comments have been deleted.

关于onEndReached 我的处理是:
onEndReached = (event) => { // load new data // hasMore: from backend data, indicates whether it is the last page, here is false // 没有任何在加载的状态, event对象不会空, 数据集大于等于每页的个数, 没有更多记录 if (this.state.isLoading || !event || dataList.length<10 || !this.state.hasMore) { return; } this.setState({ isLoading: true }); this.fetchData('Home/Search', {action:'quanziList', pageNo: ++pageIndex, uid:this.state.uid, type:this.state.type}); }
测试滑动加载,不会重复请求多次。
你的接口数据,要返回总数,以在componentWillReceiveProps里来更新hasMore。
例子地址:https://www.kuabaobao.com/quanzi
关于:cloneWithRowsAndSections 弄数据,有点怪怪的。
参考说明:https://github.com/ant-design/ant-design-mobile/blob/master/components/list-view/index.zh-CN.md
也许这个RefreshControl和ListView终要被PullToRefersh和react-infinite代替。

监测滚动的距离

onScroll = (event) => {
   if(this.state.isLoading) {
      this.state.scrollTop = event.target.scrollTop
    }
}

页面渲染完执行滚动到原位置

this.lv.scrollTo(0, this.state.scrollTop)

It's too hard!(我太难了)

Was this page helpful?
0 / 5 - 0 ratings