1.前言
触底加载思路也是常见的特效,记得最早的时候做
iOS
开发 那个时代,微博经常见上拉刷新,和下拉刷新,其实这个 触底加载,也类似可以借鉴图片懒加载思路
本节代码是在组件封装 基础上进行的,不过只看 思路也行,代码都不是事,完全可以自己搞
2. 触底加载思路分析
2.1 分析
- 既然是触底加载 那就是在触底的时候调用请求
2.所以 如何判断触底是关键
- 我们需要三个值来进行辅助判断
- 滚动条的高度:也叫卷起来的高度
滚动条卷去部分高度即可视顶端距离整个对象顶端的高度。
document.body.scrollTop || document.documentElement.scrollTop
- 所有内容高高度:
浏览器整个框架的高度,包括滚动条卷去部分+可视部分+底部隐藏部分的高度总和。
document.body.scrollHeight
- 窗口高度
window.innerHeight
2.2核心伪码
只需要判断
内容盒子的高度+滚动条的scrollTop = 盒子内容的高度
即为触底。
3. 具体代码实现
触底 肯定是滚动的时候 触底,所以需要监听滚动事件
3.1 简要分析
componentDidMount
里面获取数据,添加滚动监听触底需要在将要触底的时候 调用数据请求,所以需要一定的冗余,这个数值根据自己的实际场景来定
3.2 代码实现
componentDidMount() { this.getData() window.addEventListener("scroll", () => { // 网页滚动高度 var scrollTopHeight = document.body.scrollTop || document.documentElement.scrollTop // 文档显示区域的高度 var showHeight = window.innerHeight // 所有内容的高度 var allHeight = document.body.scrollHeight // 只需要判断内容盒子的高度+滚动条的scrollTop = 盒子内容的高度即为触底 if (allHeight - 66 < scrollTopHeight + showHeight) { console.log("触底了:",) this.getData() } }) }
4. 请求修改
4.1 简要分析
- 因为既然是触底加载,那么需要 之前的列表数据 + 当前请求的数据合起来 整合成新的列表,所以这里新旧数据的合并方式比较多,
这里选用 展开运算符来合并
this.setState({ list: [...this.state.list, ...res.data], pageNum: this.state.pageNum + 1 })
而且这个应该具备分页请求的功能,触底加载的数据,应该是新的,所以每次请求成功需要修改页码
pageNum: this.state.pageNum + 1
4.2 具体代码
//********************************** 自定义函数 getData() { const shopsUrl = "/shop/rs" let params = { latitude: 31.22967, longitude: 121.4762, offset: this.state.pageNum * this.state.pageSize, limit: this.state.pageSize } axios.get(shopsUrl, { params }).then(res => { // console.log("商铺列表数据",res) this.setState({ list: [...this.state.list, ...res.data], pageNum: this.state.pageNum + 1 }) }).catch(err => { console.log("商铺列表失败:", err) }) }
4.3 小插曲
这里的赋值有可能赋值不上去
// set数据是异步的 this.setState(() => { pageNum:this.state.pageNum+1 }) // 方式2 let pageNum2 = this.state.pageNum + 1 this.setState(() => { pageNum:pageNum2 })
5. 优化 请求时机
5.1 简要分析
- 上述的逻辑,只要一触底就会进行数据请求
- 但是这个触底操作,因为是滑动,只要移动一点点就会调用请求,所以会造成请求次数过多的问题
- 这里做一个
flag
标记,只要当前请求完成才能进行下一次请求
5.2 简要代码
- 构造函数里面
state
定义变量
constructor() { super() this.state = { list: [], pageNum: 0, pageSize: 6, flag: true, } }
设置为
true
,是因为首次进界面需要进行加载
- 请求里面变量的修改
getData() { // 请求不完成不进行下次请求 ,直接 return if (!this.state.flag) return; //下次请求开始之前 先把 flag设置为 false this.setState({ flag: false }) axios.get().then().catch()..finally(() => { this.setState({ flag: true, }) }) }
- 进行请求之前 先判断
flag
标记 ,false
直接return
- 可以进行请求的话,把
flag
标记 为false
,避免,当前请求还没完成,进行多次请求的叠加- 请求完成改为
true
- 变量的 修改 ,尤其是这种标记类的变量,一定要形成闭环,有
true
的地方,就有false
的地方
6. 加载提示
想要实现,触底加载 下方有那种 加载中的提示,可以自己写写
这里不在扩展
render() { return ( <div> <NavBar> 懂得都懂 </NavBar> <List list={this.state.list} /> <div className="tip">{this.state.msg}</div> </div> ) }
7. 完成代码
和上面的代码略有不同,做了些许优化
7.1 state
constructor() { super() this.state = { shopList: [], pageSize: 6 ,//这个可以根据自己的屏幕一屏能显示几条数据来定 pageNum:0, //当前页码 flag:true, //因为首次进页面的肯定需要请求1次 所以是 true } }
7.2 render
render() { let {shopList } = this.state return( <div> {/* 父子组件传值 */} <List shopList = {shopList}/> {/* ***************** 商铺列表 end*/} </div>) }
7.4 getData
async getData() { // 触底加载的时候 ,请求不完成 不进行下一次请求 // 不然会造成这个 请求叠加 稍微触底滑动了一点距离,就加载了 好几次请求 // 这是没有必要的,只请求1次就行,本次请求完成,下次再触底,再发请求就行 //我们做一个标识 来标记当前的 请求是否完成 // 其实和 loading动画类似的概念 let {pageSize,pageNum,flag,shopList} = this.state if(!flag)return // true进来 首先改为 false // 直到本次请求完成再改为 true // 这样就保证了 在一次请求完成的过程当中 不会进行请求叠加 // 其实也和定时器嵌套 类似 this.setState({ flag:false }) let params = { offset:shopList.length,// 已经有的数据条数 shopList.length 实际情况实际分析 // offset: pageNum*pageSize,// 已经有的数据条数 回顾 分页思想 limit: pageSize } try { let shopListRes = (await axios.get("/shop/list",{params})).data // 赋值 // this.state.shopLis 之前的/ 上一次的数据 // shopListRes 当前这次的数据 触底加载的数据 // offset怎么变 回想我们的分页加载 this.setState({ shopList:[...shopList,...shopListRes], pageNum:pageNum+1 }) return shopListRes } catch (error) { console.log("列表:", error) }finally{ this.setState({ flag:true }) } }
7.5 componentDidMount
async componentDidMount() { this.getData() // 触底加载 监听滚动事件 window.addEventListener("scroll",()=>{ //网页滚动的高度 let htmlScrollTopHeight = document.body.scrollTop || document.documentElement.scrollTop //文档显示区域的高度 let showHeight = window.innerHeight // 所有内容的高度 let allHeight = document.body.scrollHeight if(allHeight - 100 < htmlScrollTopHeight +showHeight){ console.log("触底了----------") this.getData() } }) }