This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together

Hi, boys and girls, today WE are going to share virtual list practice. In our work, we often encounter some big problems, such as the list large data rendering problem here. Obviously, hundreds of thousands of pages of data cannot be requested at once, so the back end must be paged, and the front end must do special virtual scrolling to load the corresponding pages of data. Today, let’s take a look at using virtual lists to do virtual scrolling. React + React-Window

1. What effect do you want to achieve?

First look at the renderings:

The style is a little ugly, but it doesn’t affect our function. What we want to achieve is:

  1. The total number of data items is displayed in the upper right corner.
  2. When the scrollbar scrolls, no requests are sent, and there is a skeleton screen effect where the data is empty. Stop scrolling, according to the virtual list of scrollTop to calculate the number of pages of data to request and initiate a request, request process, still maintain the skeleton screen effect, until the data request is completed, and then render. This is the virtual scrolling effect.
  3. The requested data is cached locally, and the next time it slides to the same location, there is no need to initiate a request.

Now that we know what we want to achieve, let’s do it.

2. Interface parameter and return value analysis, and technology selection

  • Back-end parameter analysis. First of all, we need to understand what kind of data the back end gives us. According to the data rendered on the page, we can deduce at least include:
{
      data: [].totalCount:300000
}
Copy the code

Data is a list of data and totalCount is the total number of data items.

  • Front-end parameter analysis. According to the functions to be done, front-end parameters can be launched at least:
{
        pageSize:20.currentPage:1,}Copy the code

PageSize = pageSize; currentPage = pageSize; pageSize = pageSize; pageSize = pageSize; pageSize = pageSize;

  • Technical selection. To implement virtual lists, I chose the React-Window library. Github has 11K stars, which is a good project. Also, the authors also built a React-Virtualized auto-Sizer library for simulating the width and height of virtualized lists.

3. React – Window, React – Virtualized – Auto – Sizer

If you want to do a good job, you must first sharpen your tools. Let’s do a preliminary understanding of these two libraries. The React window contains four react components: VariableSizeGrid, FixedSizeGrid, VariableSizeList, FixedSizeList.

  • The list keyword is the virtual list, and the Grid keyword is the virtual Grid.
  • The height of the grid with Variable keyword can be obtained through a function control, which can realize different heights of each line. Each line with the fixed keyword is the same height.

The illustration above follows:

  • FixedSizeList

  • VariableSizeList

  • FixedSizeGrid

  • VariableSizeGrid

  • Try it on your own: React-window

  • Then the Mode of the react mode is virtualized. Then the mode of the React mode is virtualized. Then the mode of the React mode is virtualized.

import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
<AutoSizer>
    {({ height, width }) => (
        <List
        className="List"
        height={height}
        itemCount={1000}
        itemSize={35}
        width={width}
        >{this is what was rendered}</List>
    )}
</AutoSizer>
Copy the code
  • Obviously, its function is to listen for changes in the width and height of the external wrap and generate real-time height and width to update the width and height of the internal virtual list.

4. Function realization

  • The fetch method is encapsulated to simulate the back-end interface. The actual project here must have an interface. It is not convenient for me to make an interface here, so I write a method to simulate the back-end interface
const fetch = (pageSize, currentPage) = > {
  return new Promise((re, rj) = > {
    setTimeout(() = > {
      const data = Array(pageSize).fill("New data");
      re({
        data,
        totalCount: 1000}); },1000);
  });
};

Copy the code
  • A one-second interface is simulated with Promise, with the parameter pageSize representing how many pieces of data to request and the parameter currentPage representing the page of data to request. As you can see, I’m not using the currentPage parameter. In a real back-end interface, these two parameters need to be used together. Fetch returns data and totalCount. Here I set totalCount to 1000 for debugging purposes, hundreds of thousands or millions is fine.

  • Encapsulate the virtual list, the code is very simple, as follows:

import React, { useEffect, useState } from "react";
import { FixedSizeList as List } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
import { debounce } from "lodash";

const ITEM_SIZE=30;// Define the height of the virtual list rows

const VisualList=() = > {
  const [list, setList] = useState([]);// Virtual list data
  const [totalCount, setTotalCount] = useState("");// Total number of entries
  return (
    <div
      className="autoSizeWrap"
      style={{ display: "flex", flexDirection: "column}} ">
      <h3 style={{ textAlign: "right}} ">Total number of entries {totalCount}</h3>
      <div style={{ flex: 1.minHeight: 0 }} className="autosizeInner">
        <AutoSizer>
          {({ width, height }) => (
            <List
              className="List"
              height={height}
              itemCount={totalCount}
              itemSize={ITEM_SIZE}
              width={width}
              onScroll={(obj)= > handleScroll(obj, height)}
              data={list}
            >
              {Row}
            </List>
          )}
        </AutoSizer>
      </div>
    </div>
  );
}
Copy the code
  • Handle the first request after the page loads:
useEffect(() = > {
 fetchList(1.20."first");// First request} []);const fetchList = async (page1, pageSize, first, page2) => {// Encapsulate the request method
  if (first === "first") { // Process the first request
    const res = await fetch(pageSize, page1);
    setList(
      res.data.concat(Array(res.totalCount - res.data.length).fill(null))); setTotalCount(res.totalCount); }else if (page2) {// Handle requests for two pages of data
    const { data } = await fetch(20, page1);
    const { data: data1 } = await fetch(20, page2);
    setListData(page1, 40, [...data, ...data1]);
  } else {// Process normal one-page data requests
    const { data } = await fetch(20, page1);
    setListData(page1, 20, data); }};const setListData = (page, size, data) = > {
    list.splice((page - 1) * 20, size, ... data);// The array splice method preserves the existing data and acts as a buffer

    setList([...list]);
};
Copy the code
  • The only thing that matters in this code is the first setList, where we only get 20 items, but we’re generating an array of length 1000.

  • Next, we’ll wrap the Row component that defines how each Row in the virtual list is rendered. The code is as follows:

const Row = (props) = > {
    const { index, style } = props;//index represents the current position of the data
    return (
        <div className="list-item" style={style}>{list[index] ? Is the current position of the list null? Row ${index} ${list[index]} ') : (<>
                    <div className={"loading-span span1} "/ >
                    <div className={"loading-span span2} "/ >// Here is the skeleton screen</>
            )}
        </div>
    );
};
Copy the code
  • Finally deal with the more complex scroll bar scrolling process, look at the code:
const handleScroll = debounce(async ({ scrollOffset }, height) => {
  if (scrollOffset === 0) return;

  const startIndex = Math.ceil(scrollOffset / ITEM_SIZE);
  const endIndex = Math.ceil((scrollOffset + height) / ITEM_SIZE);
  const startPage = Math.ceil(startIndex / 20); // The data page where the first row of the virtual list resides
  const endPage = Math.ceil(endIndex / 20); // The last row in the virtual list is the data page, 20 is the default pageSize, this is customized

  conststartIsNull = ! list[startIndex];// The first row of the virtual list is empty
  constendIsNull = ! list[endIndex];// The last row of the virtual list is empty

  const fetchType = {
    start: (startIsNull && endIsNull && startPage === endPage) || (startIsNull && ! endIsNull),// In this case, only the page of the first row is requested
    end: !startIsNull && endIsNull, // In this case, only the page data of the last row is requested
    all: startIsNull && endIsNull && startPage ! == endPage,// In this case, both the last row and the first row are requested
  };

  const fetchMethod = {
    start: fetchList.bind(this, startPage, 20),
    end: fetchList.bind(this, endPage, 20),
    all: fetchList.bind(this, startPage, 20."", endPage),
  };

  for (let key in fetchType) {
    if(fetchType[key]) { fetchMethod[key](); }}// Determine which condition is satisfied, if so, call the corresponding request request data
}, 400);
Copy the code
  • Debounce is an anti-shock treatment which is used to initiate a request after the scroll bar has been rolled.

  • The scrollOffset function is the scrollTop of the virtual list, which is the scrollTop distance of the scroll bar, and the height of the virtual list.

  • StartIndex indicates the index of the first data in the virtual list when the scroll bar stops.

  • EndIndex indicates the index of the last item in the virtual list when the scroll bar stops.

  • StartPage represents the pagination of the first data in the virtual list when the scroll bar stops.

  • EndPage represents the last page of the virtual list when the scroll bar stops.

  • If list[startIndex] is empty, or list[endIndex] is empty, the skeleton screen is displayed on the page, and the page initiates a request.

  • The judgments in fetchType handle multiple cases. That’s the startPage and endPage processing when the scrollbar scrolls, up or down, there are four possible scenarios.

  • Select * from endPage; select * from endPage;

  • StartPage and endPage data are not available. There are two cases. See the following figure for analysis:

  • StartPage has no data, only the data from startPage is requested. Take a look at the following analysis:

5. The topic later

This is the end of the virtual list, the difficulty lies in the final scroll bar processing, if you do not clearly look at the last three points.