The final code is at the end
Basic structure
The main body adopts the scrollView component of taro and sets a fixed height for it to make its sliding effective. An onScrollToLower event is configured to trigger the bottom event.
Drop down scroll component
import React from "react"; import { ScrollView } from "@tarojs/components"; import { LoadMore } from "../loadMore"; const Test = () => { const onScrollToLower = () => { // Drop to event } return ( <> <ScrollView className="test" // Set a fixed height onScrollToLower={onScrollToLower} /> <LoadMore> <> ) }
LoadMore component
Borrow AtLoadMore from taro UI
import React from "react"; import { AtLoadMore } from "taro-ui/lib"; import { View } from "@tarojs/components"; import { useSelector } from "react-redux"; import { RootState } from "@/models/index"; import "./index.scss"; export const LoadMore = () => { // Get the data in the model const showLoad = useSelector<RootState>((state) => state.smartPanHistory.showLoad); const loadingStatus = useSelector<RootState>((state) => state.smartPanHistory.loadingStatus); return ( <View className={`recordloadmore ${showLoad ? "" : "hide"}`}> <AtLoadMore status={loadingStatus} moreText='Pull up loading' /> </View> ); };
Realize splicing data
How to splice the newly loaded data behind the previous data can be borrowed from the refresh method of react, and the new data can be spliced behind by using the concat method of array. React will only update the changed part, so it will not refresh the whole page, but only refresh the new data behind.
Set the initial data to an empty array, so that even if it is the first refresh, it can be refreshed by splicing data without adding new variables to distinguish whether it is the first load or the pull-down refresh.
res.items Equivalent to []concatres.items
LoadMore control variable analysis
The LoadMore component of taro UI uses status to control three statuses: more (pull-up loading), loading (loading) and noMore (no more). At the same time, the component displays the pull-up loading data and disappears after it is completed. Therefore, a value is required to control its display and hiding. Therefore, we define two variables loadingStatus, showLoad to control the status and display of LoadMore.
- showLoad logic analysis
It is easy to think that when the data is loaded to the end, the component displays the status of pull-up loading, then displays loading, and then starts loading data. After loading data, loading ends, and the component disappears.
Therefore, the appearance of the component is directly related to the disappearance and pull-up to the end, so it should be controlled in the pull-up to the end event.
Its default value is false. It will be displayed only when the drop-down is triggered. - loadingStatus logic analysis
Pull up loading is displayed by default, and the default value of loadingStatus is more.
The display time of loading is directly related to the time of interface adjustment. In addition, loading is displayed before interface adjustment, and loading is closed after interface adjustment. Then, judge whether the component display has no more states or continue to restore the default pull-up loading according to whether there is data left. Therefore, you need to change the value of loadingStatus in the model while calling the interface, and add a hadOver variable to judge whether there is any remaining data. - hadOver logic analysis
This variable is defined by the interface. If there is an attribute indicating whether there is residual data returned in the interface, it can be used directly. If there is no attribute, the returned data length can be used for judgment. Here, the returned data length is used.
So there is the following code:
const onScrollToLower = async () => { console.log("----------I've reached the end---------"); try { await dispatch({ type: "smartPanHistory/getHistoryList", payload: { beginDate: startTime, // Query interface parameters endDate: endTime, // Query interface parameters status: current === 1 ? "2" : current === 2 ? "3" : "", // Query interface parameters limit: 20, // Query interface parameters showLoad: true, // Display components }, }); } catch (err) { err && Taro.showModal({ content: err?.info, showCancel: false }); } }
Second bottom bug
The above logic seems to be OK, but a bug will occur in actual operation
When the bottom event is triggered, let the component display, and the scroll recording the sliding of the page will be refreshed. Then, the browser will think that you have triggered the bottom event again, so this function will be executed twice, and the interface will bounce up and down and jam.
Therefore, we should judge whether the component LoadMore is displayed or hidden at the beginning of page loading, rather than letting it be displayed or hidden at the bottom.
showLoad logic secondary analysis
In the initial state, there is no data, so we should display the interface without data. When there is a lot of data (beyond the vertical display range of the mobile phone, you need to pull up to see the following data). At this time, we need to pull up. No matter what the status is, it should be displayed. Similarly, if there is only one piece of data or the data does not exceed the mobile phone interface, the component does not need to be pulled up and will not be displayed.
Therefore, the value of showLoad is related to the amount of interface data, so we should update its value according to the data length after requesting the interface. If the interface is not called before the update, its value should be false, that is, the component will not be displayed.
In this way, when the page is loaded for the first time, the data returned through the interface will show or hide LoadMore in advance. When touching the bottom, dom elements will not change, and the experience will be silky, without jamming and execution twice. (when there are more than 3 pieces of data in my page, it will exceed the range. I set it to 3, and it will appear in the request interface code below)
Logical secondary analysis of loadingStatus
The status of loadingStatus is more by default. According to the above analysis, change its status to loading before calling the interface.
Then judge according to the value of hadOver:
-
hadOver == true
At this time, there is still remaining data that can continue to trigger the pull-up function. Therefore, after calling the interface, set the loadingStatus to more and showLoad to false, and then concat enate the returned array and update it to the model.
-
hadOver == false
At this time, the subsequent request interface has no data, so set the loadingStatus status to noMore and showLoad to true.
Component disappearance bug
The above logic looks normal, but it doesn't show that there is no more, but the whole component disappears. If you look at the console, you will find that you have called the bottom function again. The reason is that there is no data at this time, but the bottom event is triggered to call the interface, and the data returned by the interface is empty. According to the first analysis of showLoad, showLoad is false and will not be displayed.
Therefore, it is necessary not to trigger the bottom event after the last monitoring of no data, but this is obviously impossible. Another way of thinking is to execute the bottom event, but add a judgment in the bottom event to judge whether to call the interface, so that various internal states of the component will not be changed and the correct state will be displayed. At the same time, there is no need to call the interface repeatedly, and this judgment variable is naturally the value of hadOver in the model.
Query function
I also have a query function to query the data in the current state of the current time period by changing the start and end times and the corresponding tabs. In this case, it is no longer a simple concat enation splicing, but to empty the data and re brush the page, and each tab has a corresponding array, When calling the interface, judge which one to update according to the incoming status value.
When querying, update the corresponding tab array to an empty array, and then refresh the corresponding data in the model to update it to the data returned by the interface instead of directly splicing data. Therefore, an isquery variable is added to judge whether it is a query function.
Here is the complete code:
Bottom event
// Remaining variables const hadOver = useSelector<RootState>((state) => state.smartPanHistory.hadOver); // Bottom function const onScrollToLower = async (currentTab) => { /** * In this step, I want to set the drop-down loading under the tabs component of taro (my three tabs), and there is a bug here * The end event will also be triggered when switching tabs. Later, it is found that the current of the control tabs is not synchronized, so an event is set before entering * check */ if(currentTab !== current) return; // Determine whether the interface is dropped by whether the data is left. If the data is left, continue to call for pull-up loading, otherwise it will not be dropped if(hadOver) { try { await dispatch({ type: "smartPanHistory/getHistoryList", payload: { beginDate: startTime, endDate: endTime, status: current === 1 ? "2" : current === 2 ? "3" : "", // Current tabs value limit: 4, // Number of requested data anchorID: anchorID, // Current pull-up anchor }, }); } catch (err) { err && Taro.showModal({ content: err?.info, showCancel: false }); } } }
model data and request interface
// Here, you can initialize the ModelState state: { allList: [], // Tabs0 data storage variable of Scrollview triggeredList: [], // Tabs1 data storage variable of Scrollview invalidList: [], // Tabs2 data storage variable of Scrollview showLoad: false, // Whether to display LoadMore components. It is not displayed by default loadingStatus: "more", // The load more component displays the status. By default, it is pulled up and loaded hadOver: true, // Whether there is still data. The default value is true. Execute the calling interface and change it according to the return value after the request anchorID: "", // Pull up data anchor isQuery: false // Is it a query }, effects: { // Query data interface *getHistoryList({ payload }: Action, { call, put, select }: EffectCommand & EffectThrottle) { // Get the payload parameter const { beginDate, endDate, limit, status, anchorID, isQuery } = payload!; // Get the data list corresponding to each tab in the model const {allList, triggeredList, invalidList } = yield select<any>(state => state.smartPanHistory); // Change the loadMore component status to loading before calling the interface yield put({ type: "updateState", payload: { loadingStatus: "loading" }, }); // Start calling interface IF011704 data let res:any = []; // If it is passed, it indicates that it is a pull-up refresh, and the anchor point is passed in if(anchorID) { // If status is passed, it means tabs == 1 or tabs == 2. If status is not passed, it defaults to tabs == 0 if (status) { res = yield call(IF011704, {beginDate, endDate, limit, status, anchorID}); } else { res = yield call(IF011704, {beginDate, endDate, limit, anchorID}); } } else { if (status) { res = yield call(IF011704, {beginDate, endDate, limit, status}); } else { res = yield call(IF011704, {beginDate, endDate, limit}); } } // Gets the length of the interface array const resLength = res.items.length; // Object parameters let parmerObj = Object.create(null); // The model data that needs to be updated in the status of no query let queryParmer = Object.create(null); // model data updated during the second query let lastAnchorID = ""; // The anchor point carried by the last data in the interface if(status) { // Determine the status to get which tabs to update, and set the corresponding updated key and value values in parmerObj if(status === "2") { // Triggered parmerObj["triggeredList"] = isQuery ? [] : triggeredList.concat(res.items); queryParmer["triggeredList"] = res.items; } else { // Invalid parmerObj["invalidList"] = isQuery ? [] : invalidList.concat(res.items); queryParmer["invalidList"] = res.items; } } else { // whole parmerObj["allList"] = isQuery ? [] : allList.concat(res.items); queryParmer["allList"] = res.items; } // If there is no data, do nothing. If there is data, get its anchor point if (resLength !== 0) { lastAnchorID = res.items[res.items.length - 1].anchorID; } // Set the pull-up and switching tabs parameters // Update the initialization status of the LoadMore component. Here I use three pieces of data to control. If it exceeds the display, it will not be displayed if it does not exceed the display parmerObj.showLoad = queryParmer.showLoad = resLength >= 3 ? true : false; // Is there any remaining status for the update data parmerObj.hadOver = queryParmer.hadOver = resLength >= limit ? true : false; // Update the LoadMore status status. If there is any remaining, it means that the drop-down loading continues to be more. If there is no data, there are no more noMore parmerObj.hadOver ? parmerObj.loadingStatus = "more" : parmerObj.loadingStatus = "noMore"; // Anchor point parmerObj.anchorID = lastAnchorID; // Update data without query status yield put({ type: "updateState", payload: parmerObj }); // When it is a query button, update the empty array to items again to refresh the data to the top isQuery && ( yield put({ type: "updateState", payload: queryParmer }) ); },