使用 React-Window实现虚拟滚动

1. 了解 React-Window

React-Window 是一个为 React 应用程序中高效渲染大数据集而设计的库。它基于窗口化或虚拟化的原则运行,这对于提高数据量大的 Web 应用程序的性能至关重要。

2. React-Window 原理

  1. 窗口化: React-Window 仅渲染用户可视区域中当前可见的元素。这最小化了 DOM 元素的数量,减少内存使用并提升性能。

  2. DOM 元素的可重用性: 用户滚动时,React-Window 重用现有的 DOM 元素来展示新项,进一步提升性能。

  3. 简化的 API: 相比于 React-Virtualized,它提供了更简单、更流畅的 API,使用起来更容易,同时功能强大。

3. 安装

通过 npm 安装 React-Window:

bash 复制代码
npm install react-window

4. 基本使用

一个基本的列表实现:

javascript 复制代码
import { FixedSizeList as List } from 'react-window';

const MyList = () => (
    <List
        height={150}
        itemCount={1000}
        itemSize={35}
        width={300}
    >
        {({ index, style }) => <div style={style}>Item {index}</div>}
    </List>
);

5. 高级使用案例和示例

5.1 自定义项目渲染器

自定义列表或网格中每个项目的渲染方式。

javascript 复制代码
import { FixedSizeList as List } from 'react-window';

// 偶数和奇数项组件
const EvenItem = ({ index }) => <div>Even: Item {index}</div>;
const OddItem = ({ index }) => <div>Odd: Item {index}</div>;

const MyCustomItem = ({ index, style }) => (
    <div style={style}>
        {index % 2 === 0 ? <EvenItem index={index} /> : <OddItem index={index} />}
    </div>
);

const MyList = () => (
    <List
        height={150}
        itemCount={1000}
        itemSize={35}
        width={300}
    >
        {MyCustomItem}
    </List>
);

5.2 动态加载

结合数据获取实现用户滚动时动态加载和渲染数据。

javascript 复制代码
import { InfiniteLoader, List } from "react-window-infinite-loader";

const loadMoreItems = /* 加载更多项目的函数 */

<InfiniteLoader
    isItemLoaded={/* 检查项目是否加载的函数 */}
    itemCount={1000}
    loadMoreItems={loadMoreItems}
>
    {({ onItemsRendered, ref }) => (
        <List
            onItemsRendered={onItemsRendered}
            ref={ref}
            {/* 其他属性 */}
        >
            {/* 项目渲染器 */}
        </List>
    )}
</InfiniteLoader>

有关此示例的更多详细信息,请查看下一章节。

5.3 性能优化

演示减少渲染 DOM 元素的数量。

javascript 复制代码
// 使用相同的 FixedSizeList 示例,但使用大量数据集
<List
    height={150}
    itemCount={100000}
    itemSize={35}
    width={300}
>
    {({ index, style }) => <div style={style}>Item {index}</div>}
</List>

6. 详细实现动态加载

为了展示如何在使用 Express 构建的后端中与 React-Window 结合实现动态加载,我们创建一个示例,前端从后端获取数据,用户通过列表滚动时请求更多数据。后端将提供分页数据,前端在达到当前加载项的末尾时请求更多数据。

使用 Express 的后端设置

  1. 创建一个简单的 Express 服务器:

首先,建立一个能够提供分页数据的 Express 服务器。

javascript 复制代码
const express = require('express');
const app = express();
const cors = require('cors');
const PORT = 3000;

app.use(cors());
// 模拟数据数组
const data = new Array(1000).fill(null).map((_, index) => ({ id: index, name: `Item ${index}` }));

// 获取分页数据的端点
app.get('/data', (req, res) => {
    const { page = 1, limit = 50 } = req.query;
    console.log('req.query:', req.query)
    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;
    res.json({
        data: data.slice(startIndex, endIndex),
        total: data.length
    });
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

在这个设置中,创建了一个模拟数据数组,/data 端点根据请求的页码和限制提供部分数据。

使用 React-Window 和无限加载的前端设置

  1. 在前端实现无限加载:

使用 React 和 React-Window 实现无限加载特性。

javascript 复制代码
import React, { useState, useEffect } from 'react';
import { FixedSizeList as List } from 'react-window';
import axios from 'axios';

const ROW_HEIGHT = 35;

function InfiniteLoadingList() {
    const [items, setItems] = useState([]);
    const [total, setTotal] = useState(0);
    const [page, setPage] = useState(1);

    const loadMoreItems = async () => {
        const { data } = await axios.get(`http://localhost:3000/data?page=${page}&limit=50`);
        setItems(prev => [...prev, ...data.data]);
        setTotal(data.total);
        setPage(prev => prev + 1);
    };

    useEffect(() => {
        loadMoreItems();
    }, []);

    useEffect(() => {
        console.log('items', items);
    }, [items]);

    const isItemLoaded = index => index < items.length;

    const renderItem = ({ index, style }) => (
        <div style={style}>
            {isItemLoaded(index) ? items[index].name : 'Loading...'}
        </div>
    );

    return (
        <List
            height={400}
            itemCount={total}
            itemSize={ROW_HEIGHT}
            width={300}
            onItemsRendered={({ visibleStopIndex }) => {
            console.log('visibleStopIndex:', visibleStopIndex)
                if (!isItemLoaded(visibleStopIndex) && items.length < total) {
                    loadMoreItems();
                }
            }}
        >
            {renderItem}
        </List>
    );
}

export default InfiniteLoadingList;

在这个 React 组件中,使用 useStateuseEffect 管理列表的状态,并在用户滚动到已加载项目末尾附近时获取新数据。FixedSizeListonItemsRendered 函数检查是否需要加载新数据。

总结

这个设置展示了在 React 应用程序中使用 React-Window 实现基本的动态加载和使用 Express 后端提供分页数据的简单实现。它有效地展示了如何通过根据需要逐步加载数据来处理大型数据集,改善性能和用户体验。

7. 结论

React-Window 对于解决 React 应用程序中渲染大型数据集相关的性能问题起着关键作用。它对虚拟化的方法,结合简化的 API,使其成为开发者的首选。通过仅渲染可见内容并高效地重用 DOM 元素,React-Window 确保即使在数据量庞大的情况下,应用程序也能保持响应性和性能。


English version: Enhancing Performance with React-Window in React Applications

1. Understanding React-Window

React-Window is a library designed for efficiently rendering large datasets in React applications. It operates on the principle of windowing or virtualization, crucial for improving the performance of web applications with substantial data sets.

2. Principles Behind React-Window

  1. Windowing: React-Window only renders elements that are currently visible in the user's viewport. This minimizes the number of DOM elements, reducing memory usage and improving performance.

  2. Reusability of DOM Elements: As the user scrolls, React-Window reuses the existing DOM elements to show new items, further enhancing performance.

  3. Simplified API: It provides a simpler and more streamlined API compared to React-Virtualized, making it easier to use while still being powerful.

3. Installation

React-Window is installed via npm:

bash 复制代码
npm install react-window

4. Basic Usage

A basic list implementation:

javascript 复制代码
import { FixedSizeList as List } from 'react-window';

const MyList = () => (
    <List
        height={150}
        itemCount={1000}
        itemSize={35}
        width={300}
    >
        {({ index, style }) => <div style={style}>Item {index}</div>}
    </List>
);

5. Advanced Use Cases and Examples

5.1 Custom Item Renderers

Customizing how each item in the list or grid is rendered.

javascript 复制代码
import { FixedSizeList as List } from 'react-window';

// Even and Odd item components
const EvenItem = ({ index }) => <div>Even: Item {index}</div>;
const OddItem = ({ index }) => <div>Odd: Item {index}</div>;

const MyCustomItem = ({ index, style }) => (
    <div style={style}>
        {index % 2 === 0 ? <EvenItem index={index} /> : <OddItem index={index} />}
    </div>
);

const MyList = () => (
    <List
        height={150}
        itemCount={1000}
        itemSize={35}
        width={300}
    >
        {MyCustomItem}
    </List>
);

5.2 Dynamic Loading

Integrating with data fetching to dynamically load and render data as the user scrolls.

javascript 复制代码
import { InfiniteLoader, List } from "react-window-infinite-loader";

const loadMoreItems = /* function to load more items */

<InfiniteLoader
    isItemLoaded={/* function to check if item is loaded */}
    itemCount={1000}
    loadMoreItems={loadMoreItems}
>
    {({ onItemsRendered, ref }) => (
        <List
            onItemsRendered={onItemsRendered}
            ref={ref}
            {/* other props */}
        >
            {/* Item renderer */}
        </List>
    )}
</InfiniteLoader>

For more details about this example, you can look at the next chapter.

5.3 Performance Optimization

Demonstrating the reduction of rendered DOM elements.

javascript 复制代码
// Using the same FixedSizeList example, but with a huge dataset
<List
    height={150}
    itemCount={100000}
    itemSize={35}
    width={300}
>
    {({ index, style }) => <div style={style}>Item {index}</div>}
</List>

6. Implement Dynamic Loading In Details

To demonstrate dynamic loading with React-Window and a backend built using Express, let's create an example where the frontend fetches data from the backend as the user scrolls through a list. The backend will provide paginated data, and the frontend will request additional data when reaching the end of the currently loaded items.

Backend Setup with Express

  1. Create a Simple Express Server:

First, set up an Express server that can serve paginated data.

javascript 复制代码
const express = require('express');
const app = express();
const cors = require('cors');
const PORT = 3000;

app.use(cors());
// Mock data array
const data = new Array(1000).fill(null).map((_, index) => ({ id: index, name: `Item ${index}` }));

// Endpoint to get paginated data
app.get('/data', (req, res) => {
    const { page = 1, limit = 50 } = req.query;
    console.log('req.query:', req.query)
    const startIndex = (page - 1) * limit;
    const endIndex = page * limit;
    res.json({
        data: data.slice(startIndex, endIndex),
        total: data.length
    });
});

app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

In this setup, a mock data array is created, and the /data endpoint serves a portion of this data based on the requested page and limit.

Frontend Setup with React-Window and Infinite Loading

  1. Implement Infinite Loading in the Frontend:

Use React and React-Window to implement the infinite loading feature.

javascript 复制代码
import React, { useState, useEffect } from 'react';
import { FixedSizeList as List } from 'react-window';
import axios from 'axios';

const ROW_HEIGHT = 35;

function InfiniteLoadingList() {
    const [items, setItems] = useState([]);
    const [total, setTotal] = useState(0);
    const [page, setPage] = useState(1);

    const loadMoreItems = async () => {
        const { data } = await axios.get(`http://localhost:3000/data?page=${page}&limit=50`);
        setItems(prev => [...prev, ...data.data]);
        setTotal(data.total);
        setPage(prev => prev + 1);
    };

    useEffect(() => {
        loadMoreItems();
    }, []);

    useEffect(() => {
        console.log('items', items);
    }, [items]);

    const isItemLoaded = index => index < items.length;

    const renderItem = ({ index, style }) => (
        <div style={style}>
            {isItemLoaded(index) ? items[index].name : 'Loading...'}
        </div>
    );

    return (
        <List
            height={400}
            itemCount={total}
            itemSize={ROW_HEIGHT}
            width={300}
            onItemsRendered={({ visibleStopIndex }) => {
            console.log('visibleStopIndex:', visibleStopIndex)
                if (!isItemLoaded(visibleStopIndex) && items.length < total) {
                    loadMoreItems();
                }
            }}
        >
            {renderItem}
        </List>
    );
}

export default InfiniteLoadingList;

In this React component, useState and useEffect are used to manage the list's state and to fetch new data when the user scrolls near the end of the loaded items. The onItemsRendered function from FixedSizeList checks if new data needs to be loaded.

Breif Summary

This setup demonstrates a basic implementation of dynamic loading with React-Window in a React application and a simple Express backend for serving paginated data. It effectively shows how you can handle large datasets by loading data incrementally as needed, improving performance and user experience.

7. Conclusion

React-Window is instrumental in solving performance issues related to rendering large datasets in React applications. Its approach to virtualization, combined with a simplified API, makes it a preferred choice for developers. By rendering only what's visible and reusing DOM elements efficiently, React-Window ensures applications remain responsive and performant, even with extensive data.

相关推荐
阿伟来咯~1 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
September_ning7 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人7 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
番茄小酱0017 小时前
Expo|ReactNative 中实现扫描二维码功能
javascript·react native·react.js
Rattenking9 小时前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
熊的猫10 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
小牛itbull14 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress