Cómo implementar el desplazamiento infinito en React
Introducción
El desplazamiento infinito se produce cuando un usuario llega al final de una página y se obtiene y carga contenido nuevo para que el usuario pueda seguir desplazándose en una experiencia relativamente fluida. Esta es una alternativa a otras soluciones de paginación que utilizan páginas numeradas o botones que cargan más contenido.
Es posible que te hayas encontrado con un desplazamiento infinito en aplicaciones como Instagram. Se te presenta un feed de imágenes y, a medida que te desplazas hacia abajo, aparecen más imágenes una y otra vez hasta que se acaba el contenido que te ofrecen.
En este tutorial, abordará los dos conceptos claves que permiten que funcione el desplazamiento infinito: detectar cuándo el usuario ha llegado al final de la página y cargar el siguiente lote de contenido para mostrar. Utilizará estos conceptos para crear una visualización de fotografías y vídeos astronómicos.
Prerrequisitos
Para completar este tutorial, necesitarás:
- Un entorno de desarrollo local para Node.js. Sigue Cómo instalar Node.js y crear un entorno de desarrollo local.
- Este tutorial utilizará la API Astronomy Picture of the Day (APOD) de la NASA. Para fines de demostración, se utilizará
DEMO_KEY
para solicitudes, pero es posible que desee registrarse para obtener una clave API si genera muchas solicitudes.
Este tutorial fue verificado con Node v14.12.0, npm
v6.14.8, react
v16.13.1, superagent
v6.1.0 y lodash.debounce
v2.7.1.
Paso 1: Configuración del proyecto
Comience a usar create-react-app
para generar una aplicación React y luego instale las dependencias:
- npx create-react-app react-infinite-scroll-example
Cambiar al nuevo directorio del proyecto:
- cd react-infinite-scroll-example
Para cargar datos desde la API de APOD, utilizará superagent
.
Para eliminar el rebote de los eventos, utilizarás lodash
.
Para agregar superagent
y lodash.debounce
a su proyecto npm
ejecutar:
- npm install superagent@6.1.0 lodash.debounce@4.0.8
Ahora, puedes ejecutar la aplicación React:
- npm start
Corrija cualquier error o problema que tenga con su proyecto y acceda a él localhost:3000
desde un navegador web.
Una vez que tenga una aplicación React en funcionamiento, puede comenzar a desarrollar su funcionalidad de desplazamiento infinito.
Paso 2 — Implementación onscrollyloadApods
El desplazamiento infinito requiere dos partes clave. Una parte será la comprobación de la posición de desplazamiento de la ventana y la altura de la ventana para determinar si el usuario ha llegado al final de la página. Otra parte será la gestión de la solicitud de información adicional para mostrar.
Comenzamos a crear un InfiniteSpace.js
archivo:
- nano src/InfiniteSpace.js
Construye tu InfiniteSpace
componente:
Fuente/InfiniteSpace.js
import React from 'react';import request from 'superagent';import debounce from 'lodash.debounce';class InfiniteSpace extends React.Component { constructor(props) { super(props); this.state = { apods: [], }; } render() { return ( div h1Infinite Space!/h1 pScroll down to load more!!/p /div ) }}export default InfiniteSpace;
El componente de desplazamiento infinito se basa en un onscroll
evento que comprobará si el usuario se ha desplazado hasta el final de la página. Al llegar al final de la página, el evento intentará cargar contenido adicional.
Al vincular eventos, especialmente eventos de desplazamiento, es una buena práctica eliminar el rebote de los eventos. La eliminación del rebote se produce cuando solo se ejecuta una función una vez que ha transcurrido una cantidad de tiempo específica desde la última llamada.
La eliminación de rebotes mejora el rendimiento para el usuario al limitar la frecuencia con la que se activa un evento y también ayuda a aliviar la tensión de los servicios que pueda estar llamando desde el controlador de eventos.
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { super(props); this.state = { apods: [], }; window.onscroll = debounce(() = { const { loadApods } = this; if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) { loadApods(); } }, 100); } // ...}
Este código establece una iteración antirrebote de 100 milisegundos.
La loadApods
función utilizará superagent
's request
para GET
la Imagen Astronómica del Día:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { // ... } dayOffset = () = { let today = new Date(); let day = today.setDate(-1 * this.state.apods.length); return new Date(day).toISOString().split('T')[0]; } loadApods = () = { request .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + 'api_key=DEMO_KEY') .then((results) = { const nextApod = { date: results.body.date, title: results.body.title, explanation: results.body.explanation, copyright: results.body.copyright, media_type: results.body.media_type, url: results.body.url }; this.setState({ apods: [ ...this.state.apods, nextApod ] }); }); } render() { // ... }}
dayOffset
Se utilizará una función para calcular la Imagen Astronómica del Día anterior.
Este código asignará la respuesta de APOD para almacenar los valores para date
, title
, explanation
, copyright
, media_type
y url
.
Los datos que se han cargado se agregarán a una matriz en el estado del componente y se iterarán en el render
método del componente.
Para verificar que las dos piezas funcionan juntas, rendericemos la respuesta:
class InfiniteSpace extends Component { // ... render() { return( div h1Infinite Space!/h1 pScroll down to load more!!/p {apods.map(apod = ( React.Fragment key={apod.date} hr / div h2{apod.title}/h2 {apod.media_type === 'image' img alt={`NASA APOD for {apod.date}`} src={apod.url} style={{ maxWidth: '100%', height: 'auto' }} / } {apod.media_type === 'video' iframe src={apod.url} width='640' height='360' style={{ maxWidth: '100%' }} /iframe } div{apod.explanation}/div div{apod.copyright}/div /div /React.Fragment ))} hr / /div ); }}
Este código mostrará un img
o un iframe
dependiendo del media_type
APOD.
En este punto, puedes modificar tu App
componente para importarlo InfiniteSpace
. Abrir App.js
:
- nano src/App.js
Y reemplace el contenido generado por Create React App con el InfiniteSpace
componente:
fuente/App.js
import React from 'react';import InfiniteSpace from './InfiniteSpace';function App() { return ( div className="App" InfiniteSpace / /div );}export default App;
En este punto, puedes ejecutar tu aplicación nuevamente:
- npm start
Corrija cualquier error o problema que tenga con su proyecto y acceda a él localhost:3000
desde un navegador web.
Si se desplaza hacia abajo en la altura de la página web, se activarán las condiciones para que onscroll
se active el evento loadApods
y debería aparecer un nuevo APOD en la pantalla.
Con estas dos piezas para el desplazamiento infinito en su lugar, se ha establecido la mayor parte del InfiniteSpace
componente. Agregar una carga inicial y un control de errores ayudará a que sea más robusto.
Paso 3: agregar una carga inicial y manejo de errores
Actualmente, InfiniteSpace
no carga ningún APOD hasta que se cumplan las condiciones del onscroll
evento. También hay tres situaciones en las que no querrás cargar APOD: si no hay más APOD para cargar, si está cargando un APOD en este momento y si se produce un error. Abordemos estos problemas.
Primero, revise InfiniteSpace.js
:
- nano src/InfiniteSpace.js
Luego, utilice componentDidMount()
para una carga inicial:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { // ... } componentDidMount() { this.loadApods(); } dayOffset = () = { // ... } loadApods = () = { // ... } render() { // ... }}
Agregue error
, hasMore
, y isLoading
al estado para abordar errores y restringir la carga innecesaria:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { super(props); this.state = { error: false, hasMore: true, isLoading: false, apods: [] }; // ... } // ...}
error
se establece inicialmente en false
. hasMore
se establece inicialmente en true
. y isLoading
se establece inicialmente en false
.
Luego, aplica el estado a onscroll
:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { constructor(props) { super(props); this.state = { error: false, hasMore: true, isLoading: false, apods: [] }; window.onscroll = debounce(() = { const { loadApods, state: { error, isLoading, hasMore } } = this; if (error || isLoading || !hasMore) return; if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) { loadApods(); } }, 100); } // ...}
Esta verificación se ejecutará de manera anticipada y evitará loadApods
que se invoque en situaciones en las que haya un error, se esté cargando actualmente o no haya APOD adicionales para cargar.
Luego, aplica el estado a loadApods
:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { // ... loadApods = () = { this.setState({ isLoading: true }, () = { request .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + 'api_key=DEMO_KEY') .then((results) = { const nextApod = { date: results.body.date, title: results.body.title, explanation: results.body.explanation, copyright: results.body.copyright, media_type: results.body.media_type, url: results.body.url }; this.setState({ hasMore: (this.state.apods.length 5), isLoading: false, apods: [ ...this.state.apods, nextApod ], }); }) .catch((err) = { this.setState({ error: err.message, isLoading: false }); }); }); } // ...}
Este código utiliza setState con una función de devolución de llamada pasada como segundo argumento. La llamada inicial a setState
en el loadApods
método establece el valor de isLoading
to true
y luego, en la función de devolución de llamada, se carga el siguiente APOD y setState
se lo vuelve a llamar para establecer isLoading
to false
.
Para los fines de nuestro tutorial, hasMore
es una comprobación booleana para limitar la cantidad de APOD a 5
. En diferentes escenarios, una API puede devolver algún valor como parte de la carga útil que indica si hay más contenido para cargar.
Si loadApods
encuentra un error, error
se establece err.message
en el catch
bloque.
Luego, aplica el estado a render
:
Fuente/InfiniteSpace.js
class InfiniteSpace extends Component { // ... render() { const { error, hasMore, isLoading, apods } = this.state; return ( div {/* ... React.Fragment ... */} {error div style={{ color: '#900' }} {error} /div } {isLoading divLoading.../div } {!hasMore divLoading Complete/div } /div ); }]
Esto ahora mostrará mensajes para error
, isLoading
, y hasMore
.
Cuando todas las piezas estén juntas, InfiniteSpace
verás así:
Fuente/InfiniteSpace.js
import React from 'react';import request from 'superagent';import debounce from 'lodash.debounce';class InfiniteSpace extends React.Component { constructor(props) { super(props); this.state = { error: false, hasMore: true, isLoading: false, apods: [] }; window.onscroll = debounce(() = { const { loadApods, state: { error, isLoading, hasMore, }, } = this; if (error || isLoading || !hasMore) return; if (window.innerHeight + document.documentElement.scrollTop === document.documentElement.offsetHeight) { loadApods(); } }, 100); } componentDidMount() { this.loadApods(); } dayOffset = () = { let today = new Date(); let day = today.setDate(-1 * this.state.apods.length); return new Date(day).toISOString().split('T')[0]; } loadApods = () = {this.setState({ isLoading: true }, () = { request .get('https://api.nasa.gov/planetary/apod?date=' + this.dayOffset() + 'api_key=DEMO_KEY') .then((results) = { const nextApod = { date: results.body.date, title: results.body.title, explanation: results.body.explanation, copyright: results.body.copyright, media_type: results.body.media_type, url: results.body.url }; this.setState({ hasMore: (this.state.apods.length 5), isLoading: false, apods: [ ...this.state.apods, nextApod ], }); }) .catch((err) = { this.setState({ error: err.message, isLoading: false }); }); }); } render() { const { error, hasMore, isLoading, apods } = this.state; return ( div style={{ padding: 10 }} h1Infinite Space!/h1 pScroll down to load more!!/p {apods.map(apod = ( React.Fragment key={apod.date} hr / div h2{apod.title}/h2 {apod.media_type === 'image' img alt={`NASA APOD for {apod.date}`} src={apod.url} style={{ maxWidth: '100%', height: 'auto' }} / } {apod.media_type === 'video' iframe src={apod.url} width='640' height='360' style={{ maxWidth: '100%' }} /iframe } div{apod.explanation}/div div{apod.copyright}/div /div /React.Fragment ))} hr / {error div style={{ color: '#900' }} {error} /div } {isLoading divLoading.../div } {!hasMore divLoading Complete/div } /div ); }}export default InfiniteSpace;
Por último, ejecute su aplicación nuevamente:
- npm start
Corrija cualquier error o problema que tenga con su proyecto y acceda a él localhost:3000
desde un navegador web.
Desplácese hacia abajo y su aplicación buscará y mostrará 5 APOD. Todas las piezas para el desplazamiento infinito se han unido.
Conclusión
En este tutorial, implementa el desplazamiento infinito en una aplicación React. El desplazamiento infinito es una solución moderna que potencialmente puede ayudar a presentar una gran cantidad de información al usuario final sin tiempos de carga iniciales prolongados.
Si su proyecto tiene contenido en el pie de página al que desea que llegue el usuario, el desplazamiento infinito puede resultar en una peor experiencia de usuario.
También existen otras bibliotecas que proporcionan esta funcionalidad y que pueden ser las más adecuadas para las necesidades de su proyecto.
Si deseas obtener más información sobre React, eche un vistazo a nuestra serie Cómo codificar en React.js o consulte nuestra página de temas de React para ver ejercicios y proyectos de programación.
Deja una respuesta