【React】react-infinite-scroll-component 实现滚动加载

本文介绍了如何使用 react-infinite-scroll-component 实现滚动加载效果,并针对滚动加载在切换月份后需要重新计数的问题提出了解决方案,即通过改变 InfiniteScroll 组件的 key 值来重置计数。同时展示了相关 CSS 自定义下拉框和滚动条样式的代码。
摘要由CSDN通过智能技术生成

react-infinite-scroll-component 实现滚动加载

效果

index.tsx

import {useState, useEffect} from 'react'
import {observer} from 'mobx-react-lite'
import styles from './index.module.scss'
import recordStore, {IData} from 'src/store/recordStore'
import {formatTimestamp} from 'src/utils'
import InfiniteScroll from 'react-infinite-scroll-component'

const TableContent: React.FC = props => {
  // 查询月份下拉框
  const [dropDown, setDropDown] = useState(false)
  const handleOpen = (e: any) => {
    e.stopPropagation()
    e.nativeEvent.stopImmediatePropagation()
    setDropDown(!dropDown)
  }
  useEffect(() => {
    document.body.addEventListener('click', () => {
      setDropDown(false)
    })
    return () => {
      document.body.removeEventListener('click', () => {
        setDropDown(false)
      })
    }
  }, [])
  const selectMonth = (e: any) => {
    // console.log(e.currentTarget.getAttribute('data-name'))
    setDropDown(!dropDown)
    const month = e.currentTarget.getAttribute('data-name')
    recordStore.setSelectedMonth(month)
  }
  // 获取最近月份记录
  useEffect(() => {
    if (recordStore.selectedMonth) {
      recordStore.fetchMonthlyData()
    }
  }, [recordStore.selectedMonth])
  return (
    <div className={styles.container}>
      <div className={styles.title}>
        <div className={styles.sum}>合计:{recordStore.totalAmount}</div>
        <div className={styles.search}>
          查询
          <div className={styles.select}>
            <ul>
              <li>
                <div className={styles.head} onClick={handleOpen}>
                  <span>{recordStore.selectedMonth}</span>
                  <span className={styles.icon_down}></span>
                </div>
                <ul className={dropDown ? styles.option : styles.hide}>
                  {recordStore.monthList &&
                    recordStore.monthList.map((item: string, index: number) => (
                      <li key={`month_${index}`} data-name={item} className={styles.item} onClick={selectMonth}>
                        {item}
                      </li>
                    ))}
                </ul>
              </li>
            </ul>
          </div>
        </div>
      </div>
      <div className={styles.table}>
        <div className={styles.thead}>
          <p>时间</p>
          <p>昵称</p>
          <p>项目</p>
          <p>数量</p>
          <p>花费</p>
        </div>
        <InfiniteScroll
          className={styles.scroll}
          dataLength={recordStore.dataList.length}
          next={() => {
            recordStore.fetchDataList()
          }}
          hasMore={recordStore.hasMore}
          height={320}
          key={recordStore.scrollKey}
        >
	        <div className={styles.tbody}>
	          {recordStore.dataList &&
	            recordStore.dataList.map((item: IData, index: number) => (
	              <div key={`data_tab1_${index}`} className={styles.row}>
	                <p>{formatTimestamp(item.createTime)}</p>
	                <p className={styles.nickname}>{item.nickname}</p>
	                <p>{item.itemName}</p>
	                <p>{item.amount}</p>
	                <p>{item.citrineAmount}</p>
	              </div>
	            ))}
	          {!recordStore.dataList && (
	            <div style={{textAlign: 'center', lineHeight: '200px', color: 'rgba(0,0,0,0.3)'}}>暂无数据</div>
	          )}
	        </div>
        </InfiniteScroll>
      </div>
    </div>
  )
}

export default observer(TableContent)

recordStore.ts

import {makeAutoObservable, runInAction} from 'mobx'
import {formatMonth} from 'src/utils'
import {getDataList, listMonth} from 'src/services/api'

export interface IData {
  nickname: string // 昵称
  createTime: number // 时间
  itemName: string // 项目名称
  itemId: number // 项目id
  amount: number // 数量
  citrineAmount: number // 花费
}

class RecordStore {
  scrollKey = 'init1' // InfiniteScroll key1
  scrollKey2 = 'init2'	// InfiniteScroll key2
  selectedMonth = '' // 选中月份
  monthList: Array<string> = [] // 月份列表
  totalAmount: number // 总额度1
  totalAmount2: number // 总额度2
  dataList: Array<IData> = [] // 数据列表1
  page = 0 // 当前页码1
  hasMore = true // 有无下一页信息1
  dataList2: Array<IData> = [] // 数据列表2
  page2 = 0 // 当前页码2
  hasMore2 = true // 有无下一页信息2
  constructor() {
    makeAutoObservable(this)
  }
  // 更改选中月份
  async setSelectedMonth(month = '') {
    runInAction(() => {
      this.selectedMonth = month
    })
  }
  // 获取月份列表
  async getMonthList() {
    const res = await listMonth()
    if (res.status === 0) {
      runInAction(() => {
        this.monthList = res.data
        this.selectedMonth = res.data.length > 0 && res.data[0]
      })
    }
  }
  // 获取月份记录
  async fetchMonthlyData(tab = 1, size = 11) {
    if (tab === 1) {
      runInAction(() => {
        this.scrollKey = Math.random().toString(36).substr(2)
        this.page = 1
        this.hasMore = true
      })
    } else {
      runInAction(() => {
        this.scrollKey2 = Math.random().toString(36).substr(2)
        this.page2 = 1
        this.hasMore = true
      })
    }
    // ↓↓↓↓↓↓↓↓↓↓-测试数据-↓↓↓↓↓↓↓↓↓↓
    const item = {
      yyno: 1,
      nickname: '昵称昵称',
      createTime: 1655275863,
      itemName: '金拱门',
      itemId: 1,
      amount: 100,
      citrineAmount: 1000,
    }
    const d: any = []
    for (let i = 0; i < 11; i++) {
      d.push(item)
    }
    // ↑↑↑↑↑↑↑↑↑↑-测试数据-↑↑↑↑↑↑↑↑↑↑
    const res = await getDataList({
      tab: tab,
      month: formatMonth(this.selectedMonth),
      size: size,
      page: tab === 1 ? this.page : this.page2,
    })
    if (res.status === 0) {
      if (tab === 1) {
        runInAction(() => {
          // ↓↓↓↓↓↓↓↓↓↓-测试数据-↓↓↓↓↓↓↓↓↓↓
          res.data.datas = d
          // ↑↑↑↑↑↑↑↑↑↑-测试数据-↑↑↑↑↑↑↑↑↑↑
          this.totalAmount = res.data.totalAmount
          this.dataList = res.data.datas
          if ((this.dataList && this.dataList.length < size) || !this.dataList) {
            this.hasMore = false
          }

          // ↓↓↓↓↓↓↓↓↓↓-测试数据加载完毕-↓↓↓↓↓↓↓↓↓↓
          if (!this.dataList || (this.dataList && this.dataList.length > 100)) {
            this.hasMore = false
          }
          // ↑↑↑↑↑↑↑↑↑↑-测试数据加载完毕-↑↑↑↑↑↑↑↑↑↑
        })
      } else {
        runInAction(() => {
          this.totalAmount2 = res.data.totalAmount
          this.dataList2 = res.data.datas && this.dataList2.concat(...res.data.datas)
          if ((this.dataList2&& this.dataList2.length < size) || !this.dataList2) {
            this.hasMore2 = false
          }
        })
      }
    }
  }
  // 分页获取帮扶/被帮扶记录
  async fetchDataList(tab = 1, size = 11) {
    if (tab === 1) {
      runInAction(() => {
        this.page++
      })
    } else {
      runInAction(() => {
        this.page2++
      })
    }
    // ↓↓↓↓↓↓↓↓↓↓-测试数据-↓↓↓↓↓↓↓↓↓↓
    const item = {
      nickname: '我的昵称',
      createTime: 1655275863,
      itemName: '麦当当',
      itemId: 1,
      amount: 100,
      citrineAmount: 1000,
    }
    const d: any = []
    for (let i = 0; i < 11; i++) {
      d.push(item)
    }
    // ↑↑↑↑↑↑↑↑↑↑-测试数据-↑↑↑↑↑↑↑↑↑↑
    const res = await getDataList({
      tab: tab,
      month: formatMonth(this.selectedMonth),
      size: size,
      page: tab === 1 ? this.page : this.page2,
    })
    if (res.status === 0) {
      if (tab === 1) {
        runInAction(() => {
          // ↓↓↓↓↓↓↓↓↓↓-测试数据-↓↓↓↓↓↓↓↓↓↓
          res.data.datas = d
          // ↑↑↑↑↑↑↑↑↑↑-测试数据-↑↑↑↑↑↑↑↑↑↑

          this.totalAmount = res.data.totalAmount
          this.dataList = res.data.datas && this.dataList.concat(...res.data.datas)
          if ((this.dataList && this.dataList.length < size) || !this.dataList) {
            this.hasMore = false
          }

          // ↓↓↓↓↓↓↓↓↓↓-测试数据加载完毕-↓↓↓↓↓↓↓↓↓↓
          if (!this.dataList || (this.dataList && this.dataList.length > 100)) {
            this.hasMore = false
          }
          // ↑↑↑↑↑↑↑↑↑↑-测试数据加载完毕-↑↑↑↑↑↑↑↑↑↑
        })
      } else {
        runInAction(() => {
          this.totalAmount2 = res.data.totalAmount
          this.dataList2 = res.data.datas && this.dataList2.concat(...res.data.datas)
          if ((this.dataList2 && this.dataList2.length < size) || !this.dataList2) {
            this.hasMore2 = false
          }
        })
      }
    }
  }
}

export default new RecordStore()

index.module.scss

li {
  list-style: none;
}
.title {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 416px;
  margin: 18px 12px 0;
  padding: 0 18px 0 22px;
  font-size: 12px;
  .sum {
    color: #ff6600;
  }
  .search {
    display: flex;
    line-height: 24px;
    ul {
      margin-left: 8px;
      width: 96px;
      height: 24px;
      border: 1px solid #dcdcdc;
      border-radius: 5px;
      background: #eeeeee;
      font-size: 12px;
      cursor: pointer;
      li {
        position: relative;
        .head {
          overflow: hidden;
          width: 100%;
          height: 24px;
          box-sizing: border-box;
          line-height: 20px;
          padding-left: 15px;
          .icon_down {
            position: absolute;
            top: 8px;
            right: 16px;
            width: 11px;
            height: 7px;
            background-image: url(../imgs/icon_down.png);
            background-size: 100%;
          }
        }
        .option {
          width: 96px;
          position: absolute;
          top: 23px;
          left: -9px;
          border: none;
          border-radius: 0;
          box-sizing: border-box;
          z-index: 1;
          .item {
            line-height: 20px;
            padding-left: 18px;
            background: #eeeeee;
            height: 24px;
            &:hover {
              background: #cccccc;
            }
          }
        }
        .hide {
          display: none;
        }
      }
    }
  }
}

.table {
  width: 416px;
  margin: 18px 0 0 12px;
  font-size: 12px;
  .thead {
    display: flex;
    align-items: center;
    height: 30px;
    margin-bottom: 4px;
    background: #f7f3ff;
    font-weight: bold;
  }
  .tbody{
    height: 330px;
  }
  //  react-infinite-scroll-component
  .row {
    display: flex;
    align-items: center;
    height: 30px;
    width: 100%;
    .nickname{
      width: 90px;
      @include text-overflow()
    }
  }
  .thead p,
  .row p {
    display: inline-block;
    text-align: center;
    &:nth-child(1) {
      width: 88px;
    }
    &:nth-child(2) {
      width: 96px;
    }
    &:nth-child(3) {
      width: 77px;
    }
    &:nth-child(4) {
      width: 70px;
    }
    &:nth-child(5) {
      width: 81px;
    }
  }
  
}

//  react-infinite-scroll-component
// 滚动条样式
*::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}
*::-webkit-scrollbar-button {
  width: 0px;
  height: 0px;
  display: none;
}
*::-webkit-scrollbar-corner {
  background-color: transparent;
}
*::-webkit-scrollbar-thumb {
  border: 4px solid rgba(0, 0, 0, 0);
  height: 6px;
  border-radius: 25px;
  background-clip: padding-box;
  background-color: #eeeeee;
}

补充

react-infinite-scroll-component 是一款滚动加载插件,在页面滚动的时候加载数据。

在页面滚动的时候,infiniteScroll 页面数是自动+1。举个例子,页面加载到第三页,infiniteScroll 无法在重新开始计数,而在某些场景中需要重新开始计数,比如上文中提到的切换月份,page number 需要重新计数。

解决方案: 改变 infiniteScroll 的 key


具体细节实现见以下博客:

  1. 【CSS】自定义下拉框
  2. 【CSS】自定义滚动条样式
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值