You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中向容器上下添加元素时如何保持滚动位置?

React双向无限滚动:顶部加载后滚动位置异常问题

需求说明

  • 用户滚动到容器底部时加载新元素
  • 同一时刻容器内元素不超过10个(优化渲染性能)
  • 向上滚动到顶部时加载历史元素

问题描述

目前功能已基本实现,但存在核心问题:向上滚动加载并在列表头部添加历史元素后,滚动位置停留在顶部,不符合预期。

  • 注:滚动到底部加载元素时无此问题,新元素渲染在底部后,滚动位置保持在列表中间,可继续滚动加载。
  • 注2:此前底部加载也有类似问题,将map的key从索引改为API返回的唯一id后解决,推测React通过key记忆元素位置,但顶部加载时该逻辑不生效,原因不明。

代码说明

  • Constants.pageSize为5,sendQuery根据传入页码返回包含5个元素的数组。
const Container = () => {
    const [pageNumber, setPageNumber] = useState(1)
    const [data, setData] = useState([])
    const lastElemRef = useRef(null)
    const firstElemRef = useRef(null)
    const isFirstRender = useFirstRender()
    const pageNumberRef = useRef(pageNumber)

    useEffect(() => {
        if(pageNumberRef.current < pageNumber){ // next page
            sendQuery(pageNumber).then((res) => {
                const prevData = data.slice(-Constants.pageSize)
                setData([...prevData, ...res])
                
            })
        }
        else{
            sendQuery(pageNumber-1).then((res) => {
                const prevData = data.slice(0, Constants.pageSize)
                setData([...res, ...prevData])
            })
        }
        pageNumberRef.current = pageNumber
    }, [pageNumber])

    const callBack = useCallback((entries) => {
        const target = entries[0]
            if(target.target.id === "load-next" && target.isIntersecting && !isFirstRender){
                setPageNumber(page => page + 1)
            }

            if(target.target.id === "load-prev" && target.isIntersecting && !isFirstRender){
                setPageNumber(page => {
                    if(page > 2){
                        return page-1
                    }else{
                        return page
                    }
                })
            }
    }, [isFirstRender, setPageNumber])

    useEffect(() => {
        observer = new IntersectionObserver(callBack, {root: document.getElementById("container"), threshold: 0.5})

        const lastElem = lastElemRef.current
        const firstElem = firstElemRef.current
        if(lastElem){
            observer.observe(lastElem)
            observer.observe(firstElem)
        }
        return (() => {
            if(lastElem){
                observer.unobserve(lastElem)
                observer.observe(firstElem)
            }
        })
    }, [callBack])

    return (
        <div id="container" className="container">
            <div ref={firstElemRef} id="load-prev"></div>
            {
                data.map((post, index) => {
                    return (
                        <Post 
                            image={post.urls.regular} 
                            by={post.user.first_name} 
                            key={post.id}
                        />
                    )
                })
            }
            <div ref={lastElemRef} style={{height: "300px"}} id="load-next"></div>
        </div>
    )
}

内容的提问来源于stack exchange,提问作者Cam4phor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 21:10:45