React移动端列表懒加载(ScrollList)

3,678 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

前言

当我们在页面展示列表的时候,数据过多时,接口通常会弄分页,前端需要传入不同的页数(page) 和 一页多少条数据(pageSize) 来掉取数据,然后依次展示即可~

条件

当你需要 一个分页的接口,展示列表的数据,需要懒加载,但不想麻烦处理接口,处理数据、处理样式的时候。 可以尝试用用这个组件

大体思路

在这里我们需要使用 ant-mobileinfiniteScroll这个组件,我们在此基础上结合接口封装,达到列表懒加载

先说说没有这个组件时候,我通常是如何处理的:在用户拉动列表到底部的时候,监听滚动条的高度,当可视区域的高度 + 滚动条滚动的高度 > 滚动内容高度 的时候做判断就OK了

  const onScroll = () => {
    let clientHeight = scrollRef.current.clientHeight; //可视区域高度
    let scrollTop  = scrollRef.current.scrollTop;  //滚动条滚动高度
    let scrollHeight = scrollRef.current.scrollHeight; //滚动内容高度
    if((clientHeight+scrollTop + 100) >(scrollHeight) && curPage <= allNumber){
    	......
    }
  }

<div onScroll={onScroll} ref={scrollRef}>

这里最好多加入点像素,以防有些时候触发不了,题外话~~~

怎样将 infiniteScroll 与 接口结合起来,形成 ScrollList 组件

首先关于 infiniteScroll 的 Api 非常简单,只有简单的三个属性

  • loadMore: 加载更多的回调函数
  • hasMore :是否还有更多内容
  • threshold: 触发加载事件的滚动触底距离阈值,单位为像素

我可以看到,最主要的就是 loadMore 这个参数,他是这个组建的核心,所以需要围绕这个属性去修改

我们通过传参来选择接口返回的数据,然后将数据累加,并判断数据的合 是否等于总数据,然后将数据返回出去,这是返回的data就可循环做一写操作,通过 childrenNode返回节点就ok了

同时也可以通过 LoadingNode来控制 加载时的样式 NoneNode来控制加载完成时的样式

代码案例

使用方法

import { useState, useEffect } from 'react';
import { ScrollList } from '@/components';
import { List } from 'antd-mobile'
import { Title } from '@/pages/commonPage'
import { getScrollList } from './services'

const Index = () => {

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

  return (
    <div style={{ padding: '6px 12px'}}>
      <ScrollList
        onRequest={getScrollList}
        payload={{
          sizeName: 'size',
          sizeNumber: 20
        }}
        childrenNode={(data:any) => {
          return <>
            <div style={{ padding: 12 }}>展示数量/总数量:{data.list.length}/{data.res.all}</div>
            <List>
              {
              data.list.map((item:any, index:any) => (
                <List.Item key={index}>{item.name}</List.Item>
              ))
            }
          </List>
          </>
        }}
      >
      </ScrollList>
    </div>
  )
}

export default Index;

详细代码

import React, { useState } from 'react';
import { InfiniteScroll, Loading } from 'antd-mobile'
import { IndexProps } from './interface.d'
import { useReactive } from 'ahooks';

/**
 * @module 无限滚动,列表懒加载
 */

function setWait(number:number) {
  return new Promise((resolve:any) => {
    setTimeout(() => {
      resolve();
    }, number);
  });
}

const Index:React.FC<IndexProps> = ({ onRequest, payload, calcData, childrenNode, LoadingNode, NoneNode, threshold = 250, wait = 1000}) => {

  let [node, setNode] = useState<React.ReactNode>(<></>)

  const state = useReactive<any>({
    hasMore: true,
    page: 1,
    number: 0,
    data: []
  })

  const loadMore = async () => {
    await setWait(wait)
    let params:any = {}
    params[payload?.pageName || 'page'] = state.page;
    params[payload?.sizeName || 'pageSize'] = payload?.sizeNumber || 10;
    const res = await onRequest(calcData ? { ...calcData(), ...payload} : params)
    const number = state.number + res[payload?.list || 'list'].length;
    const data = [...state.data, ...res[payload?.list || 'list']]
    delete res[payload?.list || 'list']
    setNode(childrenNode({ list: data, res }))
    state.hasMore = false
    state.number = number
    state.data = data
    if(res[payload?.all || 'all'] <= number){
      state.hasMore = false
    }else{
      state.hasMore = true
      state.page++
    }
  }

  const InfiniteScrollContent = ({ hasMore }: { hasMore?: boolean }) => {
    return (
      <>
        {hasMore ? LoadingNode ? (<>{LoadingNode}</>) : (
          <>
            <span>加载中</span>
            <Loading />
          </>
        ) : NoneNode ? (<>{NoneNode}</>) : (
          <span>--- 我是有底线的 ---</span>
        )}
      </>
    )
  }

  return (
    <>
      {node}
      <InfiniteScroll loadMore={loadMore} hasMore={state.hasMore} threshold={threshold}>
        <InfiniteScrollContent hasMore={state.hasMore} />
      </InfiniteScroll>
    </>
  );
}

export default Index;
export interface IndexProps {
  onRequest:any; //请求接口
  payload?: PayloadProps; //请求参数,需要将页数,第几页分开写
  calcData?: () => {}; // 其余参数
  childrenNode: (data:any) => React.ReactNode; //列表渲染的节点,data 包含两个字段,list 为每次返回列表的总和,使用这个数据渲染,res为每次请求后除list的字段
  LoadingNode?: React.ReactNode; //加载时的样式
  NoneNode?: React.ReactNode; // 加载完成时的样式
  threshold?: number; //触发加载事件的滚动触底距离阈值 单位为像素 默认 250
  wait?: number; //等待时间,如果接口速度较慢时,可设置为0, 默认为 1000 ms
}

interface PayloadProps{
  pageName?: string; // 接口页数的字段,默认 page
  sizeName?: string; // 一页数量的名字,默认为 pageSize
  sizeNumber?: number; // 一页数量,默认为 10
  all?: string; //接口返回总数的字段 默认 all
  list?: string; //接口返回总数的列表的字段 默认为 list
}

致谢