01- react+hooks实现滚动加载之坑

本人刚刚入职新公司,以前都是写Vue的,现在新公司技术栈使用的是react。一顿恶补后在实际的项目中还是避免不了踩坑,花大量的时间找原因,debug。。。不知所措的想哭QAQ
公司项目中需要实现一个滚动分页加载数据得效果,按照咱们逻辑应该是这样的:
1.请求前先判断loading是否为true, 为true时return掉阻止请求函数调用,为false时将loading设置为true然后发起请求
2.请求完毕后再将loading设置为false,允许下次再发起请求

import React, { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'
import { LoadingOutlined } from '@ant-design/icons'; 
import {getScrollLoadList} from '../../api/scrollLoad'
function ScrollLoad() {
  const [list, setList] = useState<number[]>([])
  const [pageNum, setPageNum]=useState<number>(1)
  const [loading, setLoading] = useState<boolean>(false)
  const wrapRef = useRef<any>(null)
  useEffect(() => {
    const Dom = wrapRef.current
    Dom.addEventListener('scroll',loadMore)
    return () => {
      Dom.removeEventListener('scroll',loadMore)
    }
    // eslint-disable-next-line
  },[])
  useEffect(() => {
    getList()
    // eslint-disable-next-line
  },[pageNum])
  const getList = () => {
    setLoading(true)  // 设为请求状态
    getScrollLoadList({pageNum}).then((res:any) => {
      const temp = res.result
      const nowList = pageNum === 1 ? temp : [...list,...temp]
      setList(nowList)
    }).finally(() => {
      setLoading(false)  // 请求完毕置为false
    })
  }
  const loadMore = (e:any) => {
    const {offsetHeight, scrollTop, scrollHeight} = e.target
    if(offsetHeight + scrollTop === scrollHeight) {
      if(loading) return    // 判断是否在请求状态
      setPageNum((pageNum)=> pageNum + 1)
    }
  }
  return (
    <div ref={wrapRef} className={styles.scroll_wrap}>
      {
        list && list.length && list.map(item => (
          <div key={item} className={styles.wrap_item}>{item}</div>
        ))
      }
      {loading && <div className={styles.loading}><LoadingOutlined /></div> }
    </div>
  )
}
export default ScrollLoad

咋一看好像代码没啥问题啊,但实际上问题大的去了。当连续快速的滚动时,这货始终能调用接口。loadingMore函数里的if(loading) return 并没有产生什么卵用,并且打印出来始终为我们的初始值false,百思不得其解!!!

那么为什么出现这种问题呢?经过一番研究。是因为useEffect(() => {},[])这个副作用相当于class组件内的生命周期componentDidMount,在组件渲染中只执行一次。

重点来了

当上面代码useEffect(() => {},[])执行时会产生闭包,里面用到的state变量会进行缓存,只要这个闭包不被释放,里面的state变量就不会是最新的值。即loading始终为初始状态下的值false。
那么怎么解决这个问题呢?
方案1:
可以将pageNum这个参数传进去即useEffect(() => {},[loading]),当loading改变后,会销毁之前的闭包,产生新的闭包,这样就能保证里面使用的state变量是最新的,不过这种方法每次都得重新获取dom元素,设置监听和移除监听事件,比较耗性能。(useEffect(() => {})也可以,但是不传只要有状态变化就会销毁和新建相对来说更耗性能)
改动代码如下:

useEffect(() => {
    const Dom = wrapRef.current
    Dom.addEventListener('scroll',loadMore)
    return () => {
      Dom.removeEventListener('scroll',loadMore)
    }
    // eslint-disable-next-line
  },[loading])         // 传入loading,监听loading变化

方案2
通过设置一个局部变量。 可以在函数组件外定义一个变量或者函数内使用useRef()创建一个变量(这里简称loadingRef),然后将state值loading赋值给这个变量,当loading改变时,会触发loadingRef的改变,这样就会保证loadingRef是最新的值,然后通过loadingRef活loadingRef.current 去判断即可

import React, { useEffect, useRef, useState } from 'react'
import styles from './index.module.scss'
import { LoadingOutlined } from '@ant-design/icons'; 
import {getScrollLoadList} from '../../api/scrollLoad'
// let loadingRef = false
function ScrollLoad() {
  const [list, setList] = useState<number[]>([])
  const [pageNum, setPageNum]=useState<number>(1)
  const [loading, setLoading] = useState<boolean>(false)
  const wrapRef = useRef<any>(null)
  const loadingRef = useRef<boolean>()
  loadingRef.current = loading
  // loadingRef = loading
  useEffect(() => {
    const Dom = wrapRef.current
      Dom.addEventListener('scroll',loadMore)
      console.log(Dom,loading)
    return () => {
      console.log('清空监听事件')
      Dom.removeEventListener('scroll',loadMore)
    }
    // eslint-disable-next-line
  },[])
  useEffect(() => {
    getList()
    // eslint-disable-next-line
  },[pageNum])
  const getList = () => {
    setLoading(true)
    getScrollLoadList({pageNum}).then((res:any) => {
      const temp = res.result
      const nowList = pageNum === 1 ? temp : [...list,...temp]
      setList(nowList)
    }).finally(() => {
      setLoading(false)
    })
  }
  const loadMore = (e:any) => {
    const {offsetHeight, scrollTop, scrollHeight} = e.target
    if(offsetHeight + scrollTop === scrollHeight) {
      console.log(loadingRef, '下拉加载之前')
      // if(loadingRef) return
      if(loadingRef.current) return
      setPageNum((pageNum)=> pageNum + 1)
    }
  }
  return (
    <div ref={wrapRef} className={styles.scroll_wrap}>
      {
        list && list.length && list.map(item => (
          <div key={item} className={styles.wrap_item}>{item}</div>
        ))
      }
      {loading && <div className={styles.loading}><LoadingOutlined /></div> }
    </div>
  )
}

export default ScrollLoad

案例代码:https://github.com/lee-won/reactCaseDome

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 人类发展的进程中淘汰的永远都是不思进取的守旧派。React中亦是如此思想,或许激进,但大多数人们总期待“新桃换旧符...
    DYBOY阅读 2,372评论 0 1
  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他...
    mora__阅读 571评论 0 0
  • 在开始之前,先看一张图: 为什么要推出 React Hooks? React Hooks 的设计目的,就是加强版函...
    龐校長阅读 1,551评论 1 4
  • react hooks demo 创建所需要的组件,这个项目我们分成三个组件, 分别是头部,搜索框,电影列表// ...
    Joemoninni阅读 690评论 0 3
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,548评论 0 11