介绍
无限滚动是当用户到达页面的底部,新内容被收集和加载,以便用户可以继续滚动,以实现相对无缝的体验。
你可能在像Instagram这样的应用程序中遇到无限的滚动,你被呈现了一个图像的流量,当你滚动时,更多的图像继续出现。
在本教程中,您将触及允许无限滚动工作的两个关键概念 - 检测用户何时到达页面底部,并加载下一批内容来显示。
前提条件
要完成本教程,您将需要:
- Node.js 的本地开发环境 遵循 如何安装 Node.js 并创建本地开发环境。
*本教程将使用 NASA 天文日图 (APOD) API 用于演示目的,它将使用
DEMO_KEY
用于请求,但如果您生成多个请求,您可能希望注册 API 密钥。
本教程已通过 Node v14.12.0, npm
v6.14.8, react
v16.13.1, superagent
v6.1.0 和 lodash.debounce
v2.7.1 进行验证。
步骤1 - 设置项目
开始使用 create-react-app
来生成 React App,然后安装依赖:
1npx create-react-app react-infinite-scroll-example
更改到新项目目录:
1cd react-infinite-scroll-example
要从 APOD API 上加载数据,您将使用 superagent
。
當你使用它時,你會使用它(LINK0)。
要通过npm
运行将superagent
和lodash.debounce
添加到您的项目:
1npm install [email protected] [email protected]
现在,您可以运行 React 应用程序:
1npm start
修复您的项目中的任何错误或问题,并在网页浏览器中访问localhost:3000
。
一旦你有一个工作 React 应用程序,你可以开始建立你的无限滚动功能。
步骤 2 – 实施滚动
和下载Apods
无限滚动将需要两个关键部分. 一部分将是检查窗口滚动位置和窗口的高度,以确定用户是否已经到达页面的底部。
让我们先创建一个InfiniteSpace.js
文件:
1nano src/InfiniteSpace.js
构建你的无限空间
组件:
1[label src/InfiniteSpace.js]
2import React from 'react';
3import request from 'superagent';
4import debounce from 'lodash.debounce';
5
6class InfiniteSpace extends React.Component {
7 constructor(props) {
8 super(props);
9
10 this.state = {
11 apods: [],
12 };
13 }
14
15 render() {
16 return (
17 <div>
18 <h1>Infinite Space!</h1>
19 <p>Scroll down to load more!!</p>
20 </div>
21 )
22 }
23}
24
25export default InfiniteSpace;
无限滚动组件的关键部分将是一个滚动
事件,它会检查用户是否滚动到页面的底部。
当约束事件时,特别是滚动事件时,最好是 debounce 事件,而Debouncing 是当您只运行一个函数,因为它上次被调用已经过去了一定的时间。
Debouncing 通过限制事件的频率来提高用户的性能,并有助于从事件处理器上调用任何服务。
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 constructor(props) {
4 super(props);
5
6 this.state = {
7 apods: [],
8 };
9
10 window.onscroll = debounce(() => {
11 const {
12 loadApods
13 } = this;
14
15 if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
16 loadApods();
17 }
18 }, 100);
19 }
20
21 // ...
22}
此代码建立了 100 毫秒的退出迭代。
loadApods
函数将使用superagent
的请求
来GET
当天的天文图像:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 constructor(props) {
4 // ...
5 }
6
7 dayOffset = () => {
8 let today = new Date();
9 let day = today.setDate(-1 * this.state.apods.length);
10 return new Date(day).toISOString().split('T')[0];
11 }
12
13 loadApods = () => {
14 request
15 .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
16 .then((results) => {
17 const nextApod = {
18 date: results.body.date,
19 title: results.body.title,
20 explanation: results.body.explanation,
21 copyright: results.body.copyright,
22 media_type: results.body.media_type,
23 url: results.body.url
24 };
25
26 this.setState({
27 apods: [
28 ...this.state.apods,
29 nextApod
30 ]
31 });
32 });
33 }
34
35 render() {
36 // ...
37 }
38}
dayOffset
函数将用于计算前一天的天文图像。
此代码将对 APOD 的响应进行映射,以存储日期
,标题
,解释
,版权
,media_type
和url
的值。
已加载的数据将附加到组件状态的数组中,并在组件的渲染
方法中进行迭代。
要验证你的两块工作在一起,让我们返回答案:
1class InfiniteSpace extends Component {
2 // ...
3
4 render() {
5 return(
6 <div>
7 <h1>Infinite Space!</h1>
8 <p>Scroll down to load more!!</p>
9
10 {apods.map(apod => (
11 <React.Fragment key={apod.date}>
12 <hr />
13 <div>
14 <h2>{apod.title}</h2>
15 {apod.media_type === 'image' &&
16 <img
17 alt={`NASA APOD for {apod.date}`}
18 src={apod.url}
19 style={{
20 maxWidth: '100%',
21 height: 'auto'
22 }}
23 />
24 }
25 {apod.media_type === 'video' &&
26 <iframe
27 src={apod.url}
28 width='640'
29 height='360'
30 style={{
31 maxWidth: '100%'
32 }}
33 ></iframe>
34 }
35 <div>{apod.explanation}</div>
36 <div>{apod.copyright}</div>
37 </div>
38 </React.Fragment>
39 ))}
40
41 <hr />
42 </div>
43 );
44 }
45}
此代码将显示img
或iframe
,取决于 APOD 的media_type
。
在此时,您可以修改您的应用
组件以导入InfiniteSpace
。
1nano src/App.js
并用无限空间
组件替换Create React App 生成的内容:
1[label src/App.js]
2import React from 'react';
3import InfiniteSpace from './InfiniteSpace';
4
5function App() {
6 return (
7 <div className="App">
8 <InfiniteSpace />
9 </div>
10 );
11}
12
13export default App;
此时,您可以重新运行您的应用程序:
1npm start
修复您的项目中的任何错误或问题,并在网页浏览器中访问localhost:3000
。
如果您向下滚动网页的高度,您将触发滚动
事件的条件,启动加载Apods
,并在屏幕上显示一个新的APOD。
有了这两个无限滚动的部件,你已经建立了大部分的无限空间
组件. 添加初始负载和错误处理将有助于使其更坚固。
步骤 3 – 添加初始负载和错误处理
目前,InfiniteSpace 在满足滚动
事件的条件之前不会加载任何 APOD. 还存在三种情况,您不希望加载 APOD:如果没有更多的 APOD 可加载,如果您目前正在加载 APOD,如果您遇到错误。
首先,重新浏览InfiniteSpace.js
:
1nano src/InfiniteSpace.js
然后,使用componentDidMount()
进行初始加载:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 constructor(props) {
4 // ...
5 }
6
7 componentDidMount() {
8 this.loadApods();
9 }
10
11 dayOffset = () => {
12 // ...
13 }
14
15 loadApods = () => {
16 // ...
17 }
18
19 render() {
20 // ...
21 }
22}
将错误
、hasMore
和isLoading
添加到状态中,以解决错误并限制不必要的加载:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 constructor(props) {
4 super(props);
5
6 this.state = {
7 error: false,
8 hasMore: true,
9 isLoading: false,
10 apods: []
11 };
12
13 // ...
14 }
15
16 // ...
17}
Error
最初设置为 false
; hasMore
最初设置为 true
; isLoading
最初设置为 false
。
然后,应用 stat 到onscroll
:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 constructor(props) {
4 super(props);
5
6 this.state = {
7 error: false,
8 hasMore: true,
9 isLoading: false,
10 apods: []
11 };
12
13 window.onscroll = debounce(() => {
14 const {
15 loadApods,
16 state: {
17 error,
18 isLoading,
19 hasMore
20 }
21 } = this;
22
23 if (error || isLoading || !hasMore) return;
24
25 if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
26 loadApods();
27 }
28 }, 100);
29 }
30
31 // ...
32}
此检查将提前进行,并防止在存在错误的情况下调用loadApods
,它目前正在加载,或者没有额外的APOD来加载。
然后,将状态应用到loadApods
:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 // ...
4
5 loadApods = () => { this.setState({ isLoading: true }, () => {
6 request
7 .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
8 .then((results) => {
9 const nextApod = {
10 date: results.body.date,
11 title: results.body.title,
12 explanation: results.body.explanation,
13 copyright: results.body.copyright,
14 media_type: results.body.media_type,
15 url: results.body.url
16 };
17
18 this.setState({
19 hasMore: (this.state.apods.length < 5),
20 isLoading: false,
21 apods: [
22 ...this.state.apods,
23 nextApod
24 ],
25 });
26 })
27 .catch((err) => {
28 this.setState({
29 error: err.message,
30 isLoading: false
31 });
32 });
33 });
34 }
35
36 // ...
37}
此代码使用 setState with a callback function 作为第二个参数传入。在loadApods
方法中,初始调用setState
将isLoading
的值设置为true
,然后在回调函数中,下一个 APOD 被加载,然后setState
再次调用以将isLoading
设置为false
。
对于本教程的目的,hasMore
是一个布尔式检查来限制APOD的数量为5
。在不同的场景中,API可能返回某些值作为有效负载的一部分,该值表明是否有更多内容要加载。
如果loadApods
遇到错误,则error
在catch
块中设置为err.message
。
然后,应用 stat 到render
:
1[label src/InfiniteSpace.js]
2class InfiniteSpace extends Component {
3 // ...
4
5 render() {
6 const {
7 error,
8 hasMore,
9 isLoading,
10 apods
11 } = this.state;
12
13 return (
14 <div>
15 {/* ... React.Fragment ... */}
16
17 {error &&
18 <div style={{ color: '#900' }}>
19 {error}
20 </div>
21 }
22
23 {isLoading &&
24 <div>Loading...</div>
25 }
26
27 {!hasMore &&
28 <div>Loading Complete</div>
29 }
30 </div>
31 );
32 }
33]
这将显示错误
、isLoading
和hasMore
的消息。
当所有零件都聚在一起时,无限空间
将看起来像这样:
1[label src/InfiniteSpace.js]
2import React from 'react';
3import request from 'superagent';
4import debounce from 'lodash.debounce';
5
6class InfiniteSpace extends React.Component {
7 constructor(props) {
8 super(props);
9
10 this.state = {
11 error: false,
12 hasMore: true,
13 isLoading: false,
14 apods: []
15 };
16
17 window.onscroll = debounce(() => {
18 const {
19 loadApods,
20 state: {
21 error,
22 isLoading,
23 hasMore,
24 },
25 } = this;
26
27 if (error || isLoading || !hasMore) return;
28
29 if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) {
30 loadApods();
31 }
32 }, 100);
33 }
34
35 componentDidMount() {
36 this.loadApods();
37 }
38
39 dayOffset = () => {
40 let today = new Date();
41 let day = today.setDate(-1 * this.state.apods.length);
42 return new Date(day).toISOString().split('T')[0];
43 }
44
45 loadApods = () => {this.setState({ isLoading: true }, () => {
46 request
47 .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + '&api_key=DEMO_KEY')
48 .then((results) => {
49 const nextApod = {
50 date: results.body.date,
51 title: results.body.title,
52 explanation: results.body.explanation,
53 copyright: results.body.copyright,
54 media_type: results.body.media_type,
55 url: results.body.url
56 };
57
58 this.setState({
59 hasMore: (this.state.apods.length < 5),
60 isLoading: false,
61 apods: [
62 ...this.state.apods,
63 nextApod
64 ],
65 });
66 })
67 .catch((err) => {
68 this.setState({
69 error: err.message,
70 isLoading: false
71 });
72 });
73 });
74 }
75
76 render() {
77 const {
78 error,
79 hasMore,
80 isLoading,
81 apods
82 } = this.state;
83
84 return (
85 <div style={{
86 padding: 10
87 }}>
88 <h1>Infinite Space!</h1>
89 <p>Scroll down to load more!!</p>
90
91 {apods.map(apod => (
92 <React.Fragment key={apod.date}>
93 <hr />
94 <div>
95 <h2>{apod.title}</h2>
96 {apod.media_type === 'image' &&
97 <img
98 alt={`NASA APOD for {apod.date}`}
99 src={apod.url}
100 style={{
101 maxWidth: '100%',
102 height: 'auto'
103 }}
104 />
105 }
106 {apod.media_type === 'video' &&
107 <iframe
108 src={apod.url}
109 width='640'
110 height='360'
111 style={{
112 maxWidth: '100%'
113 }}
114 ></iframe>
115 }
116 <div>{apod.explanation}</div>
117 <div>{apod.copyright}</div>
118 </div>
119 </React.Fragment>
120 ))}
121
122 <hr />
123
124 {error &&
125 <div style={{ color: '#900' }}>
126 {error}
127 </div>
128 }
129
130 {isLoading &&
131 <div>Loading...</div>
132 }
133
134 {!hasMore &&
135 <div>Loading Complete</div>
136 }
137 </div>
138 );
139 }
140}
141
142export default InfiniteSpace;
最后,再次运行您的应用程序:
1npm start
修复您的项目中的任何错误或问题,并在网页浏览器中访问localhost:3000
。
向下滚动,您的应用程序将收集并显示5个APOD。
结论
在本教程中,您在React应用程序中实施了无限滚动,无限滚动是解决潜在问题的一个现代解决方案,可以向最终用户提供大量信息,而无需大量的初始加载时间。
如果您的项目包含您希望用户访问的页面底部的内容,无限滚动可能会导致用户体验更差。
还有其他提供此功能的库,可能最适合您项目的需求。
如果您想了解更多关于 React 的信息,请查看我们的 如何在 React.js 中编码系列,或查看 我们的 React 主题页面以获取练习和编程项目。