使用React JS无限滚动


89

我正在寻找使用React实现无限滚动的方法。我遇到了react-infinite-scroll,发现它效率低下,因为它只是将节点添加到DOM中,而不会删除它们。React是否有经过验证的解决方案,可以添加,删除和维护DOM中恒定数量的节点。

这是jsfiddle问题。在这个问题中,我想一次只在DOM中包含50个元素。当用户上下滚动时,应加载和删除其他对象。由于其优化算法,我们已经开始使用React。现在我找不到解决此问题的方法。我遇到过airbnb Infinite js。但这是用Jquery实现的。要使用这个airbnb无限滚动,我必须放弃我不想做的React优化。

我想添加滚动的示例代码是(这里我正在加载所有项目。我的目标是一次仅加载50个项目)

/** @jsx React.DOM */

var Hello = React.createClass({
    render: function() {
        return (<li>Hello {this.props.name}</li>);
    }
});

var HelloList = React.createClass({ 
     getInitialState: function() {                            
         var numbers =  [];
         for(var i=1;i<10000;i++){
             numbers.push(i);
         }
         return {data:numbers};
     },

    render: function(){
       var response =  this.state.data.map(function(contact){          
          return (<Hello name="World"></Hello>);
        });

        return (<ul>{response}</ul>)
    }
});

React.renderComponent(<HelloList/>, document.getElementById('content'));

寻找帮助...

Answers:


57

基本上,在滚动时,您想确定哪些元素是可见的,然后重新渲染以仅显示那些元素,顶部和底部具有单个spacer元素来代表屏幕外的元素。

Vjeux在这里做了一个小提琴,您可以看一下:jsfiddle

滚动时执行

scrollState: function(scroll) {
    var visibleStart = Math.floor(scroll / this.state.recordHeight);
    var visibleEnd = Math.min(visibleStart + this.state.recordsPerBody, this.state.total - 1);

    var displayStart = Math.max(0, Math.floor(scroll / this.state.recordHeight) - this.state.recordsPerBody * 1.5);
    var displayEnd = Math.min(displayStart + 4 * this.state.recordsPerBody, this.state.total - 1);

    this.setState({
        visibleStart: visibleStart,
        visibleEnd: visibleEnd,
        displayStart: displayStart,
        displayEnd: displayEnd,
        scroll: scroll
    });
},

然后render函数将只显示range中的行displayStart..displayEnd

您可能也对ReactJS:双向双向无限滚动建模感兴趣。


2
这是一个很棒的技术...谢谢!但是,当recordHeight每行不同时,它将失败。我正在尝试解决这种情况。如果可以使用,我会发布它。
manalang 2014年

@manalang是否找到针对每一行不同高度的解决方案?
2014年

1
另一个要检查的项目是infinity.js(用于启发)。如果具有动态高度元素,则可以创建“页面”的概念,该概念是视口中的一组元素。假设有3个元素,第三个元素超长并且延伸到整个页面。然后,您可以说“页面高度”是最大的3个元素的大小。然后使用最小的元素高度构建虚拟节点。这样var count = pageHeight / minElementHeight。因此,即使仅渲染了3个元素,您也可以构造50个元素,但这仍将为您带来良好的性能。
Lance Pollard 2014年

14
小提琴什么都没有出现。生成按钮出现,但没有其他。
2015年

3
@ sophie-alpert:是否可以更新jsfiddle?我知道您会很忙,但是如果您可以进行更新,它将使许多像我一样的人受益:D
John Samuel

26

查看我们的React Infinite库:

https://github.com/seatgeek/react-infinite

2016年12月更新

实际上,最近我在很多项目中都使用了react-virtualized,发现它可以更好地覆盖大多数用例。两种库都很不错,这完全取决于您要查找的内容。例如,react-virtualized支持通过称为的HOC进行可变高度JIT测量CellMeasurer,示例此处为https://bvaughn.github.io/react-virtualized/#/components/CellMeasurer

2018年11月更新

来自react-virtualized的许多经验教训已移植到同一作者的更小,更快,更有效的react-window库中。


@jos:使用此库。它将删除/附加出现在视口中的DOM节点。
wle8300 '16

14
仅当您在渲染之前知道元素的高度时,此库才有效。
Druska

1
@Druska,从技术上讲是的,但是您也可以使用useWindowAsScrollContainer选项将窗口用作滚动容器。
HussienK '16

无限反应库是否支持网格?
user1261710

确实确实如此,github.com / bvaughn / react
Zach

1
import React, { Component } from 'react';
import InfiniteScroll from 'react-infinite-scroller';


const api = {
    baseUrl: '/joblist'
};

class Jobs extends Component {
    constructor(props) {
            super(props);
            this.state = {
                listData: [],
                hasMoreItems: true,
                nextHref: null
        };
    }

    fetchData(){
            var self = this;           
            var url = api.baseUrl;
            if(this.state.nextHref) {
                url = this.state.nextHref;
            }

            fetch(url)
            .then( (response) => {
                return response.json() })   
                    .then( (json) => {
                        var list = self.state.listData;                        
                        json.data.map(data => {
                            list.push(data);
                        });

                        if(json.next_page_url != null) {
                            self.setState({
                                nextHref: resp.next_page_url,
                                listData: list                               
                            });
                        } else {
                            self.setState({
                                hasMoreItems: false
                            });
                        }
                    })
                    .catch(error => console.log('err ' + error));

        }
    }

    componentDidMount() {
       this.fetchData();
    }

    render() {
    const loader = <div className="loader">Loading ...</div>;
    let JobItems; 
    if(this.state.listData){  
        JobItems = this.state.listData.map(Job => {
        return (
            <tr>
                <td>{Job.job_number}</td>
                <td>{Job.title}</td>
                <td>{Job.description}</td>
                <td>{Job.status}</td>
            </tr>
        );
      });
    }
    return (
      <div className="Jobs">
        <div className="container">
            <h2>Jobs List</h2>

            <InfiniteScroll
                pageStart={0}
                loadMore={this.fetchData.bind(this)}
                hasMore={this.state.hasMoreItems}
                loader={loader}>
                <table className="table table-bordered">
                <thead>
                    <tr>
                        <th>Job Number</th>
                        <th>Title</th>
                        <th>Description</th>
                        <th>Status</th>
                    </tr>
                </thead>
                <tbody>
                {JobItems}
                </tbody>
                </table>
            </InfiniteScroll>
        </div>
    </div>
    );
  }

}

export default Jobs;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.