原生+React實(shí)現(xiàn)懶加載(無限滾動(dòng))列表方式
應(yīng)用場景
懶加載列表或叫做無限滾動(dòng)列表,也是一種性能優(yōu)化的方式,其可疑不必一次性請(qǐng)求所有數(shù)據(jù),可以看做是分頁的另一種實(shí)現(xiàn)形式,較多適用于移動(dòng)端提升用戶體驗(yàn),新聞、資訊瀏覽等。
效果預(yù)覽
思路剖析
- 設(shè)置臨界元素,當(dāng)臨界元素進(jìn)入可視范圍時(shí)請(qǐng)求并追加新數(shù)據(jù)。
- 根據(jù)可視窗口和滾動(dòng)元素組建的關(guān)系確定數(shù)據(jù)加載時(shí)機(jī)。
container.clientHeight - wrapper.scrollTop <= wrapper.clientHeight
原生代碼實(shí)現(xiàn)
index.html
<body> <div id="wrapper" onscroll="handleScroll()"> <ul id="container"></ul> </div> <script type="text/javascript" src="./index.js"></script> </body>
index.css
* { margin: 0; padding: 0; } #wrapper { margin: 100px auto; width: 300px; height: 300px; border: 1px solid rgba(100, 100, 100, 0.2); overflow-y: scroll; } ul#container { list-style: none; padding: 0; width: 100%; } ul#container > li { height: 30px; width: 100%; } ul#container > li.green-item { background-color: #c5e3ff; } ul#container > li.red-item { background-color: #fff5d5; }
index.js
// 模擬數(shù)據(jù)構(gòu)造 const arr = []; const nameArr = ['Alice', 'July', 'Roman', 'David', 'Sara', 'Lisa', 'Mike']; let curPage = 1; let noData = false; const curPageSize = 20; const getPageData = (page, pageSize) => { if (page > 5) return []; const arr = []; // const nameArr = ['Alice', 'July', 'Roman', 'David', 'Sara', 'Lisa', 'Mike']; for (let i = 0; i < pageSize; i++) { arr.push({ number: i + (page - 1) * pageSize, name: `${nameArr[i % nameArr.length]}`, }); } return arr; }; const wrapper = document.getElementById('wrapper'); const container = document.getElementById('container'); let plainWrapper = null; /** * @method handleScroll * @description: 滾動(dòng)事件監(jiān)聽 */ const handleScroll = () => { // 當(dāng)臨界元素進(jìn)入可視范圍時(shí),加載下一頁數(shù)據(jù) if ( !noData && container.clientHeight - wrapper.scrollTop <= wrapper.clientHeight ) { curPage++; console.log(curPage); const newData = getPageData(curPage, curPageSize); renderList(newData); } }; /** * @description: 列表渲染 * @param {Array} data */ const renderList = (data) => { // 沒有更多數(shù)據(jù)時(shí) if (!data.length) { noData = true; plainWrapper.innerText = 'no more data...'; return; } plainWrapper && container.removeChild(plainWrapper); //移除上一個(gè)臨界元素 const fragment = document.createDocumentFragment(); data.forEach((item) => { const li = document.createElement('li'); li.className = item.number % 2 === 0 ? 'green-item' : 'red-item'; //奇偶行元素不同色 const text = document.createTextNode( `${`${item.number}`.padStart(7, '0')}-${item.name}` ); li.appendChild(text); fragment.appendChild(li); }); const plainNode = document.createElement('li'); const text = document.createTextNode('scroll to load more...'); plainNode.appendChild(text); plainWrapper = plainNode; fragment.appendChild(plainNode); //添加新的臨界元素 container.appendChild(fragment); }; // 初始渲染 renderList(getPageData(curPage, curPageSize));
遷移到React
在 React
中實(shí)現(xiàn)時(shí)可以省去復(fù)雜的手動(dòng)渲染邏輯部分,更關(guān)注數(shù)據(jù)。
store/data.ts
import { IDataItem } from '../interface'; const nameArr = ['Alice', 'July', 'Roman', 'David', 'Sara', 'Lisa', 'Mike']; export const getPageData = ( page: number = 1, pageSize: number = 10 ): Array<IDataItem> => { if (page > 5) return []; const arr = []; // const nameArr = ['Alice', 'July', 'Roman', 'David', 'Sara', 'Lisa', 'Mike']; for (let i = 0; i < pageSize; i++) { arr.push({ number: i + (page - 1) * pageSize, name: `${nameArr[i % nameArr.length]}`, }); } return arr; };
LazyList.tsx
/* * @Description: 懶加載列表(無限滾動(dòng)列表) * @Date: 2021-12-20 15:12:15 * @LastEditTime: 2021-12-20 16:04:18 */ import React, { FC, useCallback, useEffect, useReducer, useRef } from 'react'; import { getPageData } from './store/data'; import { IDataItem } from './interface'; import styles from './index.module.css'; export interface IProps { curPageSize?: number; } export interface IState { curPage: number; noData: boolean; listData: Array<IDataItem>; } const LazyList: FC<IProps> = ({ curPageSize = 10 }: IProps) => { const clientRef: any = useRef(null); const scrollRef: any = useRef(null); const [state, dispatch] = useReducer( (state: IState, action: any): IState => { switch (action.type) { case 'APPEND': return { ...state, listData: [...state.listData, ...action.payload.listData], }; default: return { ...state, ...action.payload }; } }, { curPage: 1, noData: false, listData: [], } ); /** * @method handleScroll * @description: 滾動(dòng)事件監(jiān)聽 */ const handleScroll = useCallback(() => { const { clientHeight: wrapperHeight } = scrollRef.current; const { scrollTop, clientHeight } = clientRef.current; // 當(dāng)臨界元素進(jìn)入可視范圍時(shí),加載下一頁數(shù)據(jù) if (!state.noData && wrapperHeight - scrollTop <= clientHeight) { console.log(state.curPage); const newData = getPageData(state.curPage, curPageSize); dispatch({ type: 'APPEND', payload: { listData: newData }, }); dispatch({ payload: { curPage: state.curPage + 1, noData: !(newData.length > 0), }, }); } }, [state.curPage, state.noData]); useEffect(() => { const newData = getPageData(1, curPageSize); dispatch({ type: 'APPEND', payload: { listData: newData }, }); dispatch({ payload: { curPage: 2, noData: !(newData.length > 0), }, }); }, []); return ( <div className={styles[`wrapper`]} ref={clientRef} onScroll={handleScroll}> <ul className={styles[`container`]} ref={scrollRef}> {state.listData.map(({ number, name }) => ( <li key={number} className={ number % 2 === 0 ? styles[`green-item`] : styles[`red-item`] } > {number}-{name} </li> ))} {<li>{state.noData ? 'no more' : 'scroll'}</li>} </ul> </div> ); }; export default LazyList;
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
React+hook實(shí)現(xiàn)聯(lián)動(dòng)模糊搜索
這篇文章主要為大家詳細(xì)介紹了如何利用React+hook+antd實(shí)現(xiàn)聯(lián)動(dòng)模糊搜索功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02react-native android狀態(tài)欄的實(shí)現(xiàn)
這篇文章主要介紹了react-native android狀態(tài)欄的實(shí)現(xiàn),使?fàn)顟B(tài)欄顏色與App顏色一致,使用戶界面更加整體。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06React中使用axios發(fā)送請(qǐng)求的幾種常用方法
本文主要介紹了React中使用axios發(fā)送請(qǐng)求的幾種常用方法,主要介紹了get和post請(qǐng)求,具有一定的參考價(jià)值,感興趣的可以了解一下2021-08-08通過React-Native實(shí)現(xiàn)自定義橫向滑動(dòng)進(jìn)度條的 ScrollView組件
開發(fā)一個(gè)首頁擺放菜單入口的ScrollView可滑動(dòng)組件,允許自定義橫向滑動(dòng)進(jìn)度條,且內(nèi)部渲染的菜單內(nèi)容支持自定義展示的行數(shù)和列數(shù),在內(nèi)容超出屏幕后,渲染順序?yàn)榭v向由上至下依次排列,對(duì)React Native橫向滑動(dòng)進(jìn)度條相關(guān)知識(shí)感興趣的朋友一起看看吧2024-02-02React實(shí)現(xiàn)翻頁時(shí)鐘的代碼示例
本文給大家介紹了React實(shí)現(xiàn)翻頁時(shí)鐘的代碼示例,翻頁時(shí)鐘把數(shù)字分為上下兩部分,翻頁效果的實(shí)現(xiàn)需要通過設(shè)置 position 把所有的數(shù)組放在同一個(gè)位置疊加起來,文中有詳細(xì)的代碼講解,需要的朋友可以參考下2023-08-08React利用props的children實(shí)現(xiàn)插槽功能
React中并沒有vue中的?slot?插槽概念?不過?可以通過props.children?實(shí)現(xiàn)類似功能,本文為大家整理了實(shí)現(xiàn)的具體方,需要的可以參考一下2023-07-07