本人剛剛入職新公司,以前都是寫Vue的螺捐,現(xiàn)在新公司技術棧使用的是react颠悬。一頓惡補后在實際的項目中還是避免不了踩坑,花大量的時間找原因定血,debug赔癌。。澜沟。不知所措的想哭QAQ
公司項目中需要實現(xiàn)一個滾動分頁加載數(shù)據(jù)得效果灾票,按照咱們邏輯應該是這樣的:
1.請求前先判斷l(xiāng)oading是否為true, 為true時return掉阻止請求函數(shù)調(diào)用,為false時將loading設置為true然后發(fā)起請求
2.請求完畢后再將loading設置為false,允許下次再發(fā)起請求
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) // 設為請求狀態(tài)
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 // 判斷是否在請求狀態(tài)
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
咋一看好像代碼沒啥問題啊茫虽,但實際上問題大的去了刊苍。當連續(xù)快速的滾動時,這貨始終能調(diào)用接口濒析。loadingMore函數(shù)里的if(loading) return 并沒有產(chǎn)生什么卵用正什,并且打印出來始終為我們的初始值false,百思不得其解:判印Sさ!
那么為什么出現(xiàn)這種問題呢?經(jīng)過一番研究莹妒。是因為useEffect(() => {},[])這個副作用相當于class組件內(nèi)的生命周期componentDidMount,在組件渲染中只執(zhí)行一次名船。
重點來了
當上面代碼useEffect(() => {},[])執(zhí)行時會產(chǎn)生閉包,里面用到的state變量會進行緩存旨怠,只要這個閉包不被釋放渠驼,里面的state變量就不會是最新的值。即loading始終為初始狀態(tài)下的值false鉴腻。
那么怎么解決這個問題呢迷扇?
方案1:
可以將pageNum這個參數(shù)傳進去即useEffect(() => {},[loading]),當loading改變后爽哎,會銷毀之前的閉包蜓席,產(chǎn)生新的閉包,這樣就能保證里面使用的state變量是最新的,不過這種方法每次都得重新獲取dom元素课锌,設置監(jiān)聽和移除監(jiān)聽事件厨内,比較耗性能。(useEffect(() => {})也可以渺贤,但是不傳只要有狀態(tài)變化就會銷毀和新建相對來說更耗性能)
改動代碼如下:
useEffect(() => {
const Dom = wrapRef.current
Dom.addEventListener('scroll',loadMore)
return () => {
Dom.removeEventListener('scroll',loadMore)
}
// eslint-disable-next-line
},[loading]) // 傳入loading,監(jiān)聽loading變化
方案2
通過設置一個局部變量雏胃。 可以在函數(shù)組件外定義一個變量或者函數(shù)內(nèi)使用useRef()創(chuàng)建一個變量(這里簡稱loadingRef),然后將state值loading賦值給這個變量,當loading改變時志鞍,會觸發(fā)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('清空監(jiān)聽事件')
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