reactjs: cuadrícula de React de desplazamiento infinito que solo representa los mosaicos de la cuadrícula visibles, pero los rec

CorePress2024-01-16  9

Explicación del problema

Tengo una cuadrícula con un montón de mosaicos cuadrados (alrededor de 120). Si solo son visibles 8 mosaicos a la vez, entonces solo quiero renderizar unos 16 mosaicos.

Los 8 mosaicos visibles, más un búfer.

Al desplazarse, cuando los 4 mosaicos superiores se desplazan fuera de la pantalla, estos mosaicos se reciclarán, se reemplazará la información que contienen con la información de los 4 mosaicos siguientes y luego se colocarán en el búfer para que se muestren. cuando el usuario se desplaza un poco más.

Esto realmente optimiza el componente si tienes muchos mosaicos, porque solo necesitas renderizar los que son visibles, más un búfer.

Referencia adicional

Este tipo de concepto se implementa en Flatlist de React Native, así como en Recycler View. He visto algunos módulos de nodo. wParece una versión reaccionar.js de una lista plana, pero no creo que la mayoría funcione, porque estoy tratando de implementar este concepto usando un diseño de tipo CSS Grid.

También he oído que este problema se describe como eliminación de cola

La mejor solución en línea

Después de observar muchos módulos de nodos, encontré un módulo de nodos de ejemplo de este concepto que utiliza un diseño de tipo cuadrícula llamado InfiniteScrollGrid, que tiene una demostración en vivo. El principal problema con este módulo de nodo es que es antiguo, no recibe mantenimiento, usa estilos antiguos de React, tiene vulnerabilidades de seguridad y ni siquiera pude compilarlo.

Otro problema es que me gustaría que los mosaicos individuales mantuvieran su relación ancho:alto original a medida que el usuario cambia el ancho de su navegador web

Solución intentada

Serbajo He intentado implementar la funcionalidad del módulo de nodo yo mismo usando ganchos. Hice todo lo posible para replicarlo, pero siento que no he entendido completamente todo el código de ese módulo de nodo.

Hay un repositorio de codesandbox y github para el siguiente ejemplo.

Nota: Por alguna razón, codesandbox tiene algunos errores que no aparecen localmente a pesar de que bifurqué el sandbox desde Github.

InfiniteScrollGrid.js

import React, {useState, useEffect, useRef, memo} from 'react';
import PropTypes from 'prop-types';
import Item from './Item';

//Inifinately scrolling grid, that only renders as many items as visible on the screen
//Based on React Native FlatList, and this Github Repo https://github.com/ggordan/react-infinite-grid
const InfiniteScrollGrid =  memo(({
    buffer = 10,
    padding = 10,
    entries = [],
    height = 250,
    width = 250,
    wrapHeight,
    lazyCallback,
    renderRangeCallback
}) => {

    if (!entries.length) return null;

    const itemHeightConst = height + (2 * padding);
    const itemWidthConst = width + (2 * padding);

    const wrapper = useRef();
    const grid = useRef();
    const [initiatedLazyload, setInitiatedLazyload] = useState(false);
    const [numEntries, setNumEntries] = useState(entries.length);
    const [minHeight, setMinHeight] = useState(2);
    const [minItemIndex, setMinItemIndex] = useState(0);
    const [maxItemIndex, setMaxItemIndex] = useState(100);
    const [wrapperHeight, setWrapperHeight] = useState(wrapHeight);

    const [itemHeight, setItemHeight] = useState(itemHeightConst);
    const [itemWidth, setItemWidth] = useState(itemHeightConst);
    const [itemGridWidth, setItemGridWidth] = useState(0);
    const [itemsPerRow, setItemsPerRow] = useState(2);

    const [initiatedLazyLoad, setInitiatedLazyLoad] = useState(false);

    const gridHeight = Math.floor(entries.length / itemsPerRow) * itemHeight;
    const getItemsPerRow = Math.floor(grid?.current?.clientWidth / itemWidthConst);
    const numVisibleRows = Math.ceil(wrapper?.current?.height / itemHeight);
    const scrolledPastRows = Math.floor((grid?.current?.height - grid?.current?.bottom) / itemHeight);

    const [scrollOffset, setScrollOffset] = useState('');

    const styles = {
        wrapper: {
            maxHeight: grid?.current?.clientHeight,
            overflowY: 'scroll',
            width: '100%',
            height: wrapperHeight,
            WebkitOverflowScrolling: true
        },
        grid: {
            position: 'relative',
            marginTop: padding,
            marginLeft: padding,
            minHeight: grid?.current?.clientHeight
        }
    };

    const totalRows = () => {
        const scrolledPastHeight = (entries.length / itemsPerRow) * itemHeightConst;
        return scrolledPastHeight < 0 ? 0 : scrolledPastHeight;
    };

    const visibleIndexes = () => {

        // The number of rows that the user has scrolled past
        let scrolledPast = Math.max((scrolledPastRows * itemsPerRow), 0);
        let min = Math.max((scrolledPast - itemsPerRow), 0);

        let bufferRows = numVisibleRows + buffer;
        let max = min(scrolledPast + (itemsPerRow * bufferRows), entries.length);

        setMinItemIndex(min);
        setMaxItemIndex(max);
    };

    const updateItemDimensions = () => {
        // setItemHeight(itemHeightConst);
        // setItemWidth(itemWidthConst);
        setItemGridWidth(grid?.current?.width);
        setItemsPerRow(getItemsPerRow());
        setMinHeight(totalRows());
    };

    const scrollListener = (event) => {
        clearTimeout(scrollOffset);
        setScrollOffset(setTimeout(() => visibleIndexes(), 10));
    };

    useEffect(() => {

        const resizeListener = () => {
            if (!wrapperHeight) setWrapperHeight(window?.innerHeight);
            updateItemDimensions();
            visibleIndexes();
        };

        window.addEventListener('resize', resizeListener);
        updateItemDimensions();
        visibleIndexes();
        return window.removeEventListener('resize', resizeListener);
    }, []);

    useEffect(() => {       //TODO: May need to disable on the first render
        if (!initiatedLazyload && (maxItemIndex === entries.length) && lazyCallback) {
            setInitiatedLazyLoad(true);
            lazyCallback(maxItemIndex);
        };
    }, [minItemIndex, maxItemIndex]);

    useEffect(() => {
        if (entries.length > numEntries) {
            setInitiatedLazyLoad(false);
            setNumEntries(entries.length);
        };
        visibleIndexes();
    }, [entries]);

    useEffect(() => {
        if (typeof renderRangeCallback === 'function') {
            renderRangeCallback(minItemIndex, maxItemIndex);    //TODO: What is this about
        };
    }, [buffer, padding, entries, height, width, wrapHeight, lazyCallback, renderRangeCallback, initiatedLazyload, numEntries, minHeight, minItemIndex, maxItemIndex, wrapperHeight, itemHeight, itemWidth, itemGridWidth, itemsPerRow, initiatedLazyLoad]);    //What are prev props and prev state

    return (
        <div
            ref={wrapper}
            className='infinite-grid-wrapper'
            onScroll={scrollListener}
            style={styles.wrapper}
        >
            <div ref={grid} className='infinite-grid' style={styles.grid}>
                {entries.slice(minItemIndex, maxItemIndex).map((entry, i) => (
                    <Item
                        key={`item-${i}`}
                        index={minItemIndex + i}
                        padding={padding}
                        width={itemWidth}
                        height={itemHeight}
                        itemsPerRow={itemsPerRow}
                        data={entry}
                    />
                ))}
            </div>
        </div>
    );

});

InfiniteScrollGrid.propTypes = {
    buffer: PropTypes.number,
    padding: PropTypes.number,
    entries: PropTypes.number,
    height: PropTypes.number,
    width: PropTypes.number,
    wrapHeight: PropTypes.number,
    lazyCallback: PropTypes.func,
    renderRangeCallback: PropTypes.func
};

export default InfiniteScrollGrid;

Artículo.js

import React, {memo} from 'react';
import PropTypes from 'prop-types';

const Item = memo(({width, itemsPerRow, index, height, padding, data}) => {

    const style = {
        width: (width / itemsPerRow) - padding,
        height: height - padding,
        left: (index % itemsPerRow) * (width / itemsPerRow),
        top: Math.floor((index / itemsPerRow) * height),
        position: 'absolute'
    };

    return (
        <div style={style} className="item">
            <div>{data}</div>
        </div>
    );
});

Item.propTypes = {
    width: PropTypes.number,
    itemsPerRow: PropTypes.number,
    index: PropTypes.number,
    height: PropTypes.number,
    padding: PropTypes.number,
    data: PropTypes.any
};

export default Item;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Ejemplo de uso

import React from 'react';
import InfiniteScrollGrid from './index';

const ExampleItem = ({index}) => (
    <div className='example'>
        This is {index}
    </div>
);

export default () => {
 
    let items = [];
    for (let i = 0; i <= 1000; i++) {
        items.push(<ExampleItem index={i} />);
    };

    const lazyCallback = (index) => {
        console.log(index);
    };
    
    return (
        <InfiniteScrollGrid entries={items} wrapperHeight={400} lazyCallback={lazyCallback} />
    );
};

Me gustaría una solución funcional para este componente de cuadrícula de desplazamiento infinito

1

Obtendrás la opción 'useEffect condicionalmente' error porque tienes if (!entries.length) return null; al comienzo de InfiniteScrollGrid. Debe asegurarse de que no haya declaraciones de devolución antes de realizar cualquier llamada a useX en su componente de función para evitar el error.

- Ben Clayton

26/03/2021 a las 20:08

@BenClayton ¡Gracias! Actualicé el sandbox y github para arreglar esa parte

- Sam

26/03/2021 a las 20:19

1

¿Probaste el paquete reaccionar-window? github.com/bvaughn/react-window

– deckele

29/03/2021 a las 21:12

¿Aceptarías una respuesta con paquetes como reaccionar-ventana?

- 0piedra0

1 de abril de 2021 a las 17:15

@0stone0 Siempre aceptaré la mejor respuesta disponible, o al menos le daré la recompensa

- Sam

1 de abril de 2021 a las 17:20



------------------------------------

Le gustaría considerar la virtualización. reaccionar-virtualizado y reaccionar-ventana son opciones bastante populares



------------------------------------

¿Quélo que desea a menudo se denomina ventana.

Recomendaría la ventana de reacción creada por el ingeniero principal de React, Brian Vaughn.

Aquí hay un ejemplo donde lo está usando.



------------------------------------

No creo que el método "react-virtualizado" o algo similar te ayudará. Estás en una aplicación y si quieres procesar muchos datos, primero debes cargarlos. Cargar grandes cantidades de datos y luego mostrarlos en una lista virtual no es un escenario real. Lo que necesitas es una FlatList extendida. La FlatList normal tiene un "umbral" valor y un valor "onEndReached" método, que essuficiente para ampliar la lista. La lista se llena de vez en cuando y la aplicación es más lenta, por lo que necesita un "umbral" valor y un valor "onStartReached" método. El "umbral" El valor se seleccionará según el tiempo de carga y el valor "alcanzado". El método carga una cantidad razonable de datos. Como no está solo con este problema, ya existen implementaciones terminadas que puede utilizar como guía. Digo como guía porque todos cocinamos solo con agua. Busqué "desplazamiento infinito bidireccional" En google y lo encontré. Recomiendo encarecidamente utilizar los componentes estándar de reaccionar nativo y ampliarlos.

¡Feliz programación!

1

Estoy hablando de reacción regular, no de reacción nativa

- Sam

2 de abril de 2021 a las 20:34

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare