我正在寻找使用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'));
寻求帮助......
答案 0 :(得分:54)
基本上在滚动时,您想要决定哪些元素是可见的,然后重新渲染以仅显示那些元素,在顶部和底部使用单个间隔元素来表示屏幕外元素。
Vjeux在这里做了一个小提琴你可以看一下:
http://jsfiddle.net/vjeux/KbWJ2/9/
滚动时执行
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
});
},
然后渲染函数将仅显示displayStart..displayEnd
范围内的行。
您可能也对ReactJS: Modeling Bi-Directional Infinite Scrolling感兴趣。
答案 1 :(得分:23)
查看我们的React Infinite Library:
https://github.com/seatgeek/react-infinite
2016年12月更新
我最近在很多项目中使用react-virtualized并发现它更好地涵盖了大多数用例。两个库都很好,它取决于你正在寻找什么。例如,react-virtualized支持通过名为CellMeasurer
的HOC进行可变高度JIT测量,例如https://bvaughn.github.io/react-virtualized/#/components/CellMeasurer。
2018年11月更新
来自react-virtualized的许多课程已被移植到来自同一作者的更小,更快,更高效的react-window库。
答案 2 :(得分:16)
使用React Js的无限滚动概念
我们可以通过监听滚动事件来进行无限滚动工作。我们可以将事件监听器添加到父最大div甚至窗口对象。
看看以下代码
render() {
return (
<div
className="vc"
ref="iScroll"
style={{ height: "420px", overflow: "auto" }}
>
<h2>Hurrah! My First React Infinite Scroll</h2>
<ul>
</ul>
</div>
);
}
上面的代码有一个简单的ul标签;在其中我们将绑定所提取的项目。这个标签被div包围,我们将在其上附加一个事件监听器来监听滚动事件。
我希望你知道这里使用的所有标签。不是吗?好吧,有些人可能不熟悉ref!因此,ref用于定义对除法(div)或任何其他html元素的引用。通过使用此引用,您可以在反应中保持该元素。我们为引用指定了名称“iScroll”,您可以使用this.refs.iScroll访问此div。
确保div的高度小于主要显示项目的总高度,否则您将无法获得滚动条。您可以将高度设置为100%或使用窗口对象而不是我们的iScroll div来在窗口级别进行滚动。
现在让我们谈谈看起来像这样的构造函数:
constructor(props) {
super(props);
this.state = {
items: 10,
loadingState: false
};
}
这里的状态对象有两个属性; items和loadingState。这些项表示可以作为lis包含在ul部分中的可用项目数,并且loadingState用于显示文本加载...正在加载数据时。 由于我们只是提供演示,这就是为什么使用数字作为项目。在实际应用中,您可能会在那里保存您的实际数据列表。
之后,您将创建一个将呈现所有项目的函数:
displayItems() {
var items = [];
for (var k = 0; k < this.state.items; k++) {
items.push(<li key={k}>Item-VoidCanvas {k}</li>);
}
return items;
}
此功能创建一个li列表并显示所有项目。再次,因为它是一个演示,我们正在使用数字显示。在真实的应用程序中,您需要迭代items数组并显示它包含的值。
立即更新渲染功能:
render() {
return (
<div
className="vc"
ref="iScroll"
style={{ height: "200px", overflow: "auto" }}
>
<h2>Hurrah! My First React Infinite Scroll</h2>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState
? <p className="loading">
loading More Items..
</p>
: ""}
</div>
);
}
是的,这只是一个显示少量lis的正常组件。它如何使它无限可滚动?希望你能记得我们之前使用过一个名为iScroll的引用,现在就开始行动了。
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (
this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
this.refs.iScroll.scrollHeight
) {
this.loadMoreItems();
}
});
}
众所周知,react组件有一个函数componentDidMount(),当该组件的模板呈现到DOM中时会自动调用它。我已经使用相同的函数添加事件监听器以滚动到我们的div iScroll。 元素的scrollTop属性将找到滚动位置,并使用clientHeight属性。 接下来,if条件将检查这两个属性的添加是否大于或等于滚动条高度。如果条件为真,则loadMoreItems函数将运行。
以下是该功能的外观:
loadMoreItems() {
if(this.state.loadingState){
return;
}
this.setState({ loadingState: true });
// you may call ajax instead of setTimeout
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
非常简单,首先将loadingState设置为true,然后显示加载div(如render函数所示)。接下来,调用setTimeout函数,这将使项目数增加10并再次使loadingState为false。 这里我们使用setTimeout的原因是生成时间延迟。在实际应用程序中,您可能会对服务器进行ajax调用,并且在解析之后,您将执行类似的操作,这是通过setTimeout的回调完成的。 完整的代码段:
class Layout extends React.Component {
constructor(props) {
super(props);
this.state = {
items: 10,
loadingState: false
};
}
componentDidMount() {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20){
this.loadMoreItems();
}
});
}
displayItems() {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(<li key={i}>Item {i}</li>);
}
return items;
}
loadMoreItems() {
if(this.state.loadingState){
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
render() {
return (
<div ref="iScroll" style={{ height: "200px", overflow: "auto" }}>
<ul>
{this.displayItems()}
</ul>
{this.state.loadingState ? <p className="loading"> loading More Items..</p> : ""}
</div>
);
}
}
ReactDOM.render(<Layout />, document.getElementById('example'));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="example"></div>
答案 3 :(得分: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;