preface
Long list rendering is a common rendering requirement in the front end, usually when we are faced with a large list of data, instead of directly fetching and rendering all the content at once (because this would be slow and affect the user experience), we use some technology that allows the list to be rendered gradually.
Let’s take a look at some of the solutions to long list rendering. This article refers directly to the long list of implementations of the more well-known component libraries and attempts to restore a simplified source code for a solution that does not depend on the component libraries themselves.
This note contains three parts. The first part is the implementation of ANTD pager list, the second part is the implementation of the infinite dropdown list, and the third part is the actual part. The final purpose is to implement a dropdown list under the premise that React does not depend on ANTD component library.
1. Antd and Pagination
Ant Design is the front-end team of Ant Group. Ant Design is also a well-known component library in the React component ecosystem. Here we mainly refer to the paged List of components under the List page.
1.1 Antd List source code
Let’s go to Github and copy the source code of the Ant-Design repository locally (“version”: “4.16.8”), all the components of Ant-Design are in the Components directory. Go to that directory, open the List folder, and open the index. TSX file to see the core logic of the list.
A component library has a lot of detailed logic to work with, and we try to ignore these details when reading the source code, making achieving the goal our main concern.
Here we focus on how ANTD implements paging. Let’s take a look at the object properties (props –,) associated with paging lists.
constpaginationProps = { ... defaultPaginationProps,total: dataSource.length,
current: paginationCurrent,
pageSize: paginationSize, ... (pagination || {}), };Copy the code
The first parameter defaultPaginationProps is hardcoded in the component function,
const defaultPaginationProps = {
current: 1.total: 0};Copy the code
The second argument, total, represents the total length of the list. The default value passed to the dataSource in the component function props is an empty list, i.e.,
function List<T> ({
//. dataSource = [],//. })
Copy the code
The third parameter, ‘current’, is set by the React state.
const [paginationCurrent, setPaginationCurrent] = React.useState(
paginationObj.defaultCurrent || 1,);// paginationObj is also from props. If not, set it to 1
Copy the code
The fourth parameter pageSize indicates how many items to display on a page, which is also set by state,
const [paginationSize, setPaginationSize] = React.useState(paginationObj.defaultPageSize || 10);
// The default is 10 projects
Copy the code
The above parameters are important, so let’s go through them again to better understand the logic behind “switch page” and “render Item” (note that we use an Item inside the Item list).
-
Total: Total length, that is, the total number of items in the list
-
PageSize: the number of items under a page, with total/pageSize = largestPage, where largestPage indicates the page number of the largestPage
-
Current: Indicates the page number of the current page, which satisfies 1 <= current <= largestPage
Now, the key point is, with these numbers, how do we implement the logic to render the current page?
The number of Items displayed on the current page is equivalent to the number of pageSize Items from (current-1) * pageSize, which is the index of all Items on the current page. The logic in the source code is as follows,
let splitDataSource = [...dataSource]; if (pagination) { if (dataSource.length > (paginationProps.current - 1) * paginationProps.pageSize) { splitDataSource = [...dataSource].splice( (paginationProps.current - 1) * paginationProps.pageSize, paginationProps.pageSize, ); }}Copy the code
The first argument to splice is the starting point of the index, and the second argument is the number of items to be retrieved from the original list. This function returns this data to form a new array.
Finally, add a piece of code so that current does not exceed largestPage,
const largestPage = Math.ceil(paginationProps.total / paginationProps.pageSize); if (paginationProps.current > largestPage) { paginationProps.current = largestPage; } Copy the code
All props are passed to the Pagination subcomponent. How does this function work
Note that the above code only contains the logic to switch the page number. In fact, you need to select the internally rendered item based on the page number each time you render. This logic is also straightforward, so it is omitted here.
1.2 From ANTD to RC-Pagination
In fact, ant-Design’s list paging logic is not built entirely from scratch. It also relies on code written by the React Component organization. In ant-Design’s package.json file, you can see many package dependencies starting with rc-. These are all components written by the React Component organization, and ANTD is encapsulated on top of it.
This pagination list, for example, is built on top of the rC-pagination package. Let’s see how it works.
import React, { useState } from "react"; import RCPagination from "rc-pagination"; import "rc-pagination/assets/index.css"; import "./App.css"; function App() { const [current, setCurrent] = useState(1); function onChange(page) { setCurrent(page); } return ( <> <RCPagination onChange={onChange} current={current} pageSize={3} total={25} /> </> ); } export default App; Copy the code
As shown below,
This is as minimal a demo as possible. As you can see, ant-Design uses the rc-pagination property name directly, such as current, total, and pageSize. In addition, onChange is a switch function for paging. It also needs to be implemented manually and passed to the RCPagination component.
I was going to go to Github and clone a repository, open it./ SRC /index.js, and it looks like this.
export { default } from './Pagination'; Copy the code
Ok, let’s go directly to pagination. JSX in the same directory.
The page bar implementation is over 700 lines long and introduces two other components, options. JSX (170 lines) and paper. JSX (30 lines). The logic for a small page list is so complicated that Antd needs to stand on the shoulders of giants.
1.3 rc – pagination source code
Here, let’s just break down the logic of the page jump display. The relationship between page jump and UI is to display the page number that can be clicked on the left and right sides of the current page. For example, in the example in the picture above, when we are on page 5, we only show up to page 3 and page 7, which is the logic that needs to be set manually. The source code for setting this section is as follows:
const getJumpPrevPage = () = > Math.max(1.this.state.current - (this.props.showLessItems ? 3 : 5)); const getJumpNextPage = () = > Math.min( calculatePage(undefined.this.state, this.props), this.state.current + (this.props.showLessItems ? 3 : 5));function calculatePage(p, state, props) { const pageSize = typeof p === 'undefined' ? state.pageSize : p; return Math.floor((props.total - 1) / pageSize) + 1; } Copy the code
Note that the source code for rC-Pagination also uses Class writing, so there will be a lot of this. In addition, the showLessItems property is a bool, which controls how wide the bar can be stretched. (For example, the figure above shows a width of 5. If showLessItems === true, then the width is 3, then only 4, 5, and 6 in the middle will be displayed).
Rc has a lot of source code are dealing with page number display and jump problems, because RC-Pagination pagination bar support a variety of custom functions, also need to do very stable (can’t bug), so the source code is more complex, more custom use please see the official website example.
Antd and Scroller List
Dropdown loading lists are also a common requirement, where when the user pulls down to the bottom of the page, the next page automatically triggers a function to add new items to the list. At the moment, nuggets and Toutiao are taking this approach.
The component of the Antd drop-down list is directly used by a third party library called ‘React-infinite-Scroller’. We went directly to the Github repository to see the source code of the library and understand how the drop-down is implemented.
The library has 300 lines of core code, and the most important function is scrollListener, which is mounted to the target DOM element using the native addEventListener method when the page is initially rendered.
Let’s look at the contents of the function, which is quite long (with some comments for reading).
scrollListener() {
// Get the current element and the parent element
const el = this.scrollComponent; // Reference via React ref
const scrollEl = window;
const parentNode = this.getParentElement(el);
/ / the offset
let offset;
// Calculate the offset based on the parent element (container)
if (this.props.useWindow) {
const doc =
document.documentElement || document.body.parentNode || document.body;
constscrollTop = scrollEl.pageYOffset ! = =undefined
? scrollEl.pageYOffset
: doc.scrollTop;
if (this.props.isReverse) {
offset = scrollTop;
} else {
offset = this.calculateOffset(el, scrollTop); }}else if (this.props.isReverse) {
offset = parentNode.scrollTop;
} else {
offset = el.scrollHeight - parentNode.scrollTop - parentNode.clientHeight;
}
// Determine whether to load the next page based on the offset
if (
offset < Number(this.props.threshold) && (el && el.offsetParent ! = =null)) {this.detachScrollListener();
this.beforeScrollHeight = parentNode.scrollHeight;
this.beforeScrollTop = parentNode.scrollTop;
// Call loadMore after detachScrollListener to allow for non-async loadMore functions
if (typeof this.props.loadMore === 'function') {
this.props.loadMore((this.pageLoaded += 1));
this.loadMore = true; }}}Copy the code
There are two key points,
offset
: This offset records the distance to the bottom of the current position of the list container, followed by a series ofif-else
To set this value ifuseWindow === true
, then directly put the entire window (orhtml
Tag, or the parent element of the list itemthis.getParentElement(el)
As a container, which prompts us to place long lists with the elements in the next layer as the container by default.loadMore
: The bottom half of the function content is a judgment statement, if sliding distance to the bottom of the distanceoffset
The value is less than the thresholdthis.props.threshold
(the default is 250), which triggers the function to load the next pageloadMore
Then put the state of the current pagethis.pageLoaded
Add one for subsequent loading.
The rest of the source code is filled with details like calculateOffset, detachMousewheelListener, detachScrollListener, and so on, which you can read for yourself if you’re interested.
Three, the drop-down list combat
Next, we try to get away from the ANTD component library and use react-infinite-Scroller directly to implement an infinite drop-down list. A full online demo of CodesandBox is available at the end of this article.
Note that the React-infinite-Scroller library is “React-only,” which means we don’t even need to install it via NPM, but instead copy the source directly to the local and use it as if we implemented it ourselves.
However, for convenience, we will import the library.
I’m going to start with the initialization variables, and the rest of it is modeled after the ANTD example,
const fetchItemEachNumber = 5;
const fetchItemTotalNumber = 100;
const fakeDataUrl = `https://randomuser.me/api/? results=${fetchItemEachNumber}&inc=name,gender,email,nat&noinfo`;
Copy the code
We use the API provided by the randomuser.me website to get the data. Here we set to get five pieces of data at a time (fetchItemEachNumber) and stop loading when the amount of data exceeds 100 (fetchItemTotalNumber).
We maintain three states, one is the long list of data, one is loading, the state is true when we are loading data, so we can set the loading time animation based on the state change (not in this case), and the last state is hasMore. This state is used to indicate whether new data is available to be loaded. When the number of loads reaches 100, this state changes to false.
const initState = { data: [].loading: false.hasMore: true };
const [state, setState] = useState(initState);
Copy the code
The following is the behavior after the page is initialized. First, grab the data once and show the first 5 items.
useEffect(() = > {
fetchData((res) = > {
setState((state) = > {
return { ...state, data: res.results }; }); }); } []);// Retrieve the data using the reqWest library
const fetchData = (callback) = > {
reqwest({
url: fakeDataUrl,
type: "json".method: "get".contentType: "application/json".success: (res) = >{ callback(res); }}); };Copy the code
Then there’s the drop-down list that triggers a new page, like this,
const handleInfiniteOnLoad = () = > {
let { data } = state;
// Set the state to loading
setState((state) = > {
return { ...state, loading: true };
});
// The data volume is full
if (data.length > fetchItemTotalNumber) {
setState((state) = > {
return { ...state, hasMore: false.loading: false };
});
return;
}
// Load the new data
fetchData((res) = > {
const fetchedData = data.concat(res.results);
setState((state) = > {
return { ...state, data: fetchedData, loading: false };
});
});
};
Copy the code
And finally the React Element that comes back,
<article>
<div className="container">
<InfiniteScroll
initialLoad={false}
pageStart={0}
loadMore={handleInfiniteOnLoad}
hasMore={! state.loading && state.hasMore}
useWindow={false}
>
<>
{state.data.map((item) => {
return (
<section className="inner" key={item.email}>
{item.name.last}
</section>
);
})}
</>
</InfiniteScroll>
</div>
</article>
Copy the code
In this case, our container is a
Finally, you need to set up some CSS to perform the dropdown and auto-load actions. See the full code and online demo at the end of this article
(Thank you for reading, writing is not easy, please give more support, there are mistakes or shortcomings of the place also please give more advice)
Online demo: Codeboxsand link 🔗