使用
//使用上拉加载组件页面的state
this.state = {
style: "comment",
dataList: [], // 数据列表
interval: 5, // 分页间隔
params: { // 数据请求参数
page: 1,
pageSize: 5,
action: "comment"
},
leaveState: {} // 离开时缓存状态
}
//使用上拉加载组件页面的方法 需要参数接收数据列表
setData = (dataList, initial) => {
if (initial && !dataList.length) {
// 首次数据请求 并且 没有数据 走空页面
this.setState({ noReview: true })
} else {
this.setState({ noReview: false, dataList })
}
}
setLeaveState = leaveState => {
this.setState({ leaveState })
}
// 使用上拉加载组件页面的render
// 需要用到上拉加载的区域 用PullOnloading包裹起来
// callback 获取数据的方法 api 不走redux
// interval 分页间隔
// params 请求数据的参数(初始参数)
// setData 获取上拉加载的数据列表并设置到state的的方法
// isSigned 是否登录 通过高阶组件获取 withAuth -> withUser
// setLeaveState 设置离开时获取缓存数据的凭证
<div className={styles.messageOutBox}>
<Nav value={"评论"} />
<PullOnloading
callback={getMessages}
interval={interval}
params={params}
setData={this.setData}
isSigned={this.props.isSigned}
setLeaveState={this.setLeaveState}
>
<CommonBody
dataList={dataList}
style={style}
state={{ source: leaveState }}
/>
</PullOnloading>
</div>
上拉加载组件
状态预设:
loaded: false -> 请求数据 false请求完成 true请求中 取名有问题
loading: true -> 第一次加载状态 初始为true 只要有一次请求完成即为false
emptyStatus: false -> 请求报错改为true 显示404页面
btnShow: false -> 按钮是否显示 加载状态时不显示 加载完成才能显示
方法
scroll = () => {
const wrapper = this.wrapperRef.current //加载文字的元素
const wrapperTop = wrapper.getBoundingClientRect().top
// 获取加载文字元素距离屏幕顶部的距离
const windowHeight = window.screen.height
// 屏幕的高度
const { timeCount, curPage, totalPage, interval } = this.state
// 清楚定时器
if (timeCount) clearTimeout(timeCount)
// 加载文字出现在屏幕中
// 当前页数小于总页数
// 当前页数不是分页间隔的倍数
if (
wrapperTop < windowHeight &&
curPage < totalPage &&
curPage % interval !== 0
) {
this.setState({ loadingText: "加载中" })
// 满足条件后 触发数据请求 loadMore
this.setState({
timeCount: setTimeout(this.loadMore, 500)
})
}
}
loadMore = async type => {
const { callback, setData } = this.props
const { curPage, loaded, dataList, params, interval } = this.state
if (!loaded) {
await this.setState(
{
loaded: true,
btnShow: false
},
() => {
if (!type || type === "initial") {
this.setState({ curPage: curPage + 1 })
}
}
)
this.timer = setTimeout(() => {
params.page = this.state.curPage
callback(params)
.then(res => {
this.setState(
{
dataList: [...dataList, ...res.list],
loaded: false,
params
},
async () => {
if (!this.state.dataList.length) {
this.setState({ noData: true })
}
const totalPage = Math.ceil(res.total / params.pageSize)
if (type === "initial") {
// 首次数据请求
await setData(this.state.dataList, "initial")
} else {
await setData(this.state.dataList)
}
this.setState({
loading: false,
emptyStatus: false,
btnShow: true,
totalPage
})
// 内容未撑满一屏 还有内容 继续发送请求
if (this.state.dataList.length) {
const innerBox = this.innerRef.current
const wrapper = this.wrapperRef.current
const wrapperTop = wrapper.getBoundingClientRect().top
const windowHeight = window.screen.height
if (
innerBox.clientHeight < windowHeight &&
wrapperTop - 20 < windowHeight &&
this.state.curPage < totalPage
) {
if (this.state.curPage % interval == 0) return
setTimeout(() => {
this.loadMore()
}, 500)
}
}
}
)
})
.catch(e => {
console.log(e)
this.setState({ loading: false, emptyStatus: true })
})
}, 200)
}
}
// 点击上一页处理
prevPage = async () => {
const { setData, interval } = this.props
const { curPage } = this.state
// 获取点击上一页之后的页数
// 例如:间隔为5 当前页数为6
// 将6最大化到所在分页的最大页数 10 (不满分页间隔的倍数都处理成分页间隔的倍数)
// 再减去分页间隔数的两倍
const pageNum = Math.ceil(curPage / interval) * interval - 2 * interval
// 下一条要请求的数据就是所得的pageNum再加1
await this.setState({
curPage: pageNum + 1,
dataList: [],
btnShow: false,
loadingText: "加载中"
})
// 清空数据列表 显示加载中文字
await setData(this.state.dataList)
// 再发送请求
setTimeout(() => {
this.loadMore("prevPage")
}, 500)
}
nextPage = async () => {
// 点击下一页处理
const { setData } = this.props
const { curPage } = this.state
await this.setState({
dataList: [],
btnShow: false,
loadingText: "加载中",
curPage: curPage + 1
})
// 清空数据列表 继续请求下一条数据
await setData(this.state.dataList)
setTimeout(() => {
this.loadMore("nextPage")
}, 500)
}
加载中显示
隐藏:
当前页数是分页间隔的倍数 或者 当前页数等于总页数
<div
className={classNames(
styles.loadMoreLoad,
(curPage % interval == 0 || curPage == totalPage) &&
styles.loadMoreHidden
)}
ref={this.wrapperRef}
>
<img className={styles.loading} src={loadingGif} alt="" />
{loadingText}
</div>
上下页按钮
// btnShow: true 数据请求完成
// 总页数大于分页间隔数
// 当前页数等于总页数 或者 当前页数是分页间隔的倍数
// 总页数大于零
// 同时满足上述条件时 按钮出现
// 上一页按钮隐藏 当前页数等于分页间隔时
// 即第一页到底了 要去第二页了 没有上一页
// 下一页按钮隐藏 当前页数等于总页数
// 即最后一页了 没有下一页
{btnShow &&
totalPage >= interval &&
(curPage === totalPage || curPage % interval === 0) &&
totalPage > 0 && (
<div className={classNames(styles.pullBtnBoxLoad)}>
<button
className={classNames(
styles.pullBtnLoad,
curPage === interval && styles.hiddenBtnLoad
)}
onClick={this.prevPage}
>
上一页
</button>
<button
className={classNames(
styles.pullBtnLoad,
curPage === totalPage && styles.hiddenBtnLoad
)}
onClick={this.nextPage}
>
下一页
</button>
</div>
)}
页面缓存处理
1、
import withCache from "../../containers/cache"
2、export default compose(withCache)(PullOnloading)
3、componentWillUnmount() { window.removeEventListener("scroll", this.scroll, false) clearTimeout(this.state.timeCount) // 记录 if (this.props.isSigned) { this.props.setCacheData({ // TODO: 数据兼容: 旧代码没有将type放在params里做处理 // 存储在session storage的参数是扁平的 params: { params: this.state.params, dataList: this.state.dataList, curPage: this.state.curPage, btnShow: this.state.btnShow, totalPage: this.state.totalPage, loadingText: this.state.loadingText, interval: this.props.interval } }) } }
4、constructor里面
const cached = props.getCacheData()
if (cached) { this.cached = true this.state = { timeCount: null, loaded: false, loadingText: cached.params.loadingText, curPage: cached.params.curPage, dataList: cached.params.dataList, totalPage: cached.params.totalPage, loading: false, emptyStatus: false, btnShow: cached.params.btnShow, interval: cached.params.interval, params: cached.params.params } }
5、
this.props.setLeaveState(this.props.cacheKey)
将缓存凭证传给使用页面
import React from "react"
import { withRouter } from "react-router-dom"
import styles from "./index.module.less"
import classNames from "classnames"
import propTypes from "prop-types"
import Blank from "../../component/blank"
import ErrorBlank from "../blank-page"
import ERROR_IMG from "../../static/img/error-img.png"
import loadingGif from "../../static/img/loading.gif"
import withCache from "../../containers/cache"
import { compose } from "redux"
/*
<PullOnloading
callback={getMessages} // api方法 返回Promise
params={params} // api 参数 包括page、pageSize
interval={3} // 分页页距 例:每三次请求换一页
setData={this.setData} // 设置要用的数据列表
>
{调用的组件的内容区域}
</PullOnloading>
调用的组件
setData = dataList => {
// console.log(dataList)
this.setState({ dataList })
}
*/
@withRouter
class PullOnloading extends React.PureComponent {
constructor(props) {
super(props)
const cached = props.getCacheData()
if (cached) {
this.cached = true
this.state = {
timeCount: null,
loaded: cached.params.loaded,
loadingText: cached.params.loadingText,
curPage: cached.params.curPage,
dataList: cached.params.dataList,
totalPage: cached.params.totalPage,
loading: cached.params.loading,
emptyStatus: cached.params.emptyStatus,
btnShow: cached.params.btnShow,
interval: cached.params.interval,
params: cached.params.params,
noData: cached.params.noData
}
} else {
this.state = {
timeCount: null,
loaded: false,
loading: true,
emptyStatus: false,
loadingText: "加载中",
curPage: 0,
dataList: [],
btnShow: false,
params: props.params,
interval: props.interval,
totalPage: 0,
noData: false
}
}
this.outterRef = React.createRef()
this.innerRef = React.createRef()
this.wrapperRef = React.createRef()
}
componentDidMount() {
const cached = this.props.getCacheData()
this.props.setLeaveState(this.props.cacheKey)
this.props.setData(this.state.dataList)
const { curPage, interval } = this.state
if (curPage % interval !== 0 || curPage === 0) {
if (!cached) {
this.loadMore("initial")
}
}
window.addEventListener("scroll", this.scroll, false)
}
componentWillUnmount() {
window.removeEventListener("scroll", this.scroll, false)
clearTimeout(this.state.timeCount)
// 记录
if (this.props.isSigned) {
this.props.setCacheData({
// TODO: 数据兼容: 旧代码没有将type放在params里做处理
// 存储在session storage的参数是扁平的
params: {
params: this.state.params,
dataList: this.state.dataList,
curPage: this.state.curPage,
btnShow: this.state.btnShow,
totalPage: this.state.totalPage,
loadingText: this.state.loadingText,
interval: this.state.interval,
loaded: this.state.loaded,
loading: this.state.loading,
emptyStatus: this.state.emptyStatus,
noData: this.state.noData
}
})
}
}
get blankPage() {
return (
<ErrorBlank
source={"error"}
img={ERROR_IMG}
btn={"返回首页"}
tips={"404,您要找的页面去火星了~~"}
/>
)
}
loadMore = async type => {
const { callback, setData } = this.props
const { curPage, loaded, dataList, params, interval } = this.state
if (!loaded) {
await this.setState(
{
loaded: true,
btnShow: false
},
() => {
if (!type || type === "initial") {
this.setState({ curPage: curPage + 1 })
}
}
)
this.timer = setTimeout(() => {
params.page = this.state.curPage
callback(params)
.then(res => {
this.setState(
{
dataList: [...dataList, ...res.list],
loaded: false,
params
},
async () => {
if (!this.state.dataList.length) {
this.setState({ noData: true })
}
const totalPage = Math.ceil(res.total / params.pageSize)
if (type === "initial") {
await setData(this.state.dataList, "initial")
} else {
await setData(this.state.dataList)
}
this.setState({
loading: false,
emptyStatus: false,
btnShow: true,
totalPage
})
// 内容未撑满一屏 还有内容 继续发送请求
if (this.state.dataList.length) {
const innerBox = this.innerRef.current
const wrapper = this.wrapperRef.current
const wrapperTop = wrapper.getBoundingClientRect().top
const windowHeight = window.screen.height
if (
innerBox.clientHeight < windowHeight &&
wrapperTop - 20 < windowHeight &&
this.state.curPage < totalPage
) {
if (this.state.curPage % interval == 0) return
setTimeout(() => {
this.loadMore()
}, 500)
}
}
}
)
})
.catch(e => {
console.log(e)
this.setState({ loading: false, emptyStatus: true })
})
}, 200)
// 数值为0 会出问题
}
}
scroll = () => {
const wrapper = this.wrapperRef.current
const wrapperTop = wrapper.getBoundingClientRect().top
const windowHeight = window.screen.height
const { timeCount, curPage, totalPage, interval } = this.state
if (curPage >= totalPage) return
if (timeCount) clearTimeout(timeCount)
if (
wrapperTop < windowHeight &&
curPage < totalPage &&
curPage % interval !== 0
) {
this.setState({ loadingText: "加载中" })
this.setState({
timeCount: setTimeout(this.loadMore, 500)
})
}
}
backTop = () => {
const dom = document.documentElement || document.body
dom.scrollTop = 0
}
prevPage = async () => {
const { setData, interval } = this.props
const { curPage } = this.state
const pageNum = Math.ceil(curPage / interval) * interval - 2 * interval
await this.setState({
curPage: pageNum + 1,
dataList: [],
btnShow: false,
loadingText: "加载中"
})
await setData(this.state.dataList)
setTimeout(() => {
this.loadMore("prevPage")
}, 500)
}
nextPage = async () => {
const { setData } = this.props
const { curPage } = this.state
await this.setState({
dataList: [],
btnShow: false,
loadingText: "加载中",
curPage: curPage + 1
})
await setData(this.state.dataList)
setTimeout(() => {
this.loadMore("nextPage")
}, 5000)
}
render() {
const {
loadingText,
curPage,
totalPage,
loading,
emptyStatus,
btnShow,
interval,
noData
} = this.state
const { background = {} } = this.props
console.log(curPage)
console.log(totalPage)
return (
<div
className={styles.outerBoxLoad}
ref={this.outterRef}
style={background}
>
{emptyStatus ? (
this.blankPage
) : (
<div className={classNames(styles.innerBoxLoad)} ref={this.innerRef}>
<div className={styles.onloadingChildrenBoxLoad}>
{this.props.children}
</div>
{!noData && (
<div
className={classNames(
styles.loadMoreLoad,
(curPage % interval == 0 || curPage == totalPage) &&
styles.loadMoreHidden
)}
ref={this.wrapperRef}
>
<img className={styles.loading} src={loadingGif} alt="" />
{loadingText}
</div>
)}
{btnShow &&
totalPage >= interval &&
(curPage === totalPage || curPage % interval === 0) &&
totalPage > 0 && (
<div className={classNames(styles.pullBtnBoxLoad)}>
<button
className={classNames(
styles.pullBtnLoad,
curPage === interval && styles.hiddenBtnLoad
)}
onClick={this.prevPage}
>
上一页
</button>
<button
className={classNames(
styles.pullBtnLoad,
curPage === totalPage && styles.hiddenBtnLoad
)}
onClick={this.nextPage}
>
下一页
</button>
</div>
)}
</div>
)}
{loading && <Blank />}
</div>
)
}
}
PullOnloading.propTypes = {
callback: propTypes.func,
interval: propTypes.number,
setData: propTypes.func,
params: propTypes.object
}
export default compose(withCache)(PullOnloading)