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

函数组件内部scroll事件无法获取正确Context值求助

问题描述

我正在构建一个可分批展示大量列表项的组件,当用户向下滚动窗口时,若存在更多项则自动加载。但遇到appState变量表现不一致的问题:在组件顶部打印能获取Context中的正确值(items长度为100),但在onScroll函数中始终返回默认未初始化状态(items长度为0)。我确认未违反Hook规则,请问问题出在哪里?


组件代码

import { useContext } from 'react'
import { useLifecycles} from 'react-use'
import AppState from '../../models/AppState'
import { Context } from '../../store/create'

export default () => {
    const appState:AppState = useContext(Context)
    console.log('appState.items (root)=', appState.items.length) // 正确返回100

    useLifecycles(
        () => {
            window.addEventListener('scroll', onScroll)
        },
        () => {
            window.removeEventListener('scroll', onScroll)
        }
    )

    const onScroll = (evt:any) => {
        console.log('appState.items (onScroll)', appState.items.length) // 始终返回0(默认未初始化状态)
    }

    return (
        <div className='ItemList'>
            <h1>Hello world</h1>
            {/* 列表项渲染位置 */}
        </div>
    )
}

../../store/create 代码

import React from 'react'
import AppState, { getDefaultState } from '../models/AppState'

let state:AppState = getDefaultState()
export const Context:React.Context<AppState> = React.createContext<AppState>(state)

export const setAppState = (newState:AppState):void => {
  _state = newState
}

export const getAppState = ():AppState => {
  return _state
}

问题根源与解决方案

核心问题1:Context 状态更新逻辑错误

你的create文件里,更新状态时只是修改了局部变量_state(代码还存在笔误:应该是state = newState,不是_state),但React Context不会因为局部变量变化自动更新。React Context的更新必须通过Context.Provider的value属性变化触发,你当前完全没用到Provider,组件拿到的始终是createContext时传入的默认初始值。

核心问题2:闭包陷阱

就算修复了Context的使用方式,当前代码仍存在闭包问题:onScroll函数在组件首次渲染时创建,捕获了当时的appState值。后续组件重新渲染时,新的onScroll会捕获新的appState,但旧的onScroll仍绑定在window上,引用的还是首次渲染的旧状态。


修复步骤

1. 重构Context的状态管理

必须用Context.Provider包裹组件树,通过React状态API(useState/useReducer)维护Context值,确保状态变化时触发组件更新:

import React, { useState, createContext, useContext } from 'react'
import AppState, { getDefaultState } from '../models/AppState'

const Context = createContext<{
  state: AppState;
  setState: (newState: AppState) => void;
} | null>(null)

export const AppStateProvider = ({ children }: { children: React.ReactNode }) => {
  const [state, setState] = useState<AppState>(getDefaultState())
  return (
    <Context.Provider value={{ state, setState }}>
      {children}
    </Context.Provider>
  )
}

export const useAppState = () => {
  const context = useContext(Context)
  if (!context) throw new Error('useAppState必须在AppStateProvider内部使用')
  return context
}

之后在根组件中用AppStateProvider包裹需要访问状态的组件。

2. 修复滚动监听的闭包问题

有两种可行方案:

  • 方案一:用useRef保存最新状态
    通过useRef实时同步最新的appState,让onScroll能访问到当前最新值:

    import { useRef } from 'react'
    import { useLifecycles} from 'react-use'
    import { useAppState } from '../../store/create'
    
    export default () => {
        const { state: appState } = useAppState()
        const appStateRef = useRef(appState)
        appStateRef.current = appState // 每次渲染更新ref的current值
    
        console.log('appState.items (root)=', appState.items.length)
    
        useLifecycles(
            () => {
                window.addEventListener('scroll', onScroll)
            },
            () => {
                window.removeEventListener('scroll', onScroll)
            }
        )
    
        const onScroll = (evt:any) => {
            console.log('appState.items (onScroll)', appStateRef.current.items.length)
        }
    
        return (
            <div className='ItemList'>
                <h1>Hello world</h1>
                {/* 列表项渲染位置 */}
            </div>
        )
    }
    
  • 方案二:依赖appState重新绑定监听
    用useEffect替代useLifecycles,将appState加入依赖数组,状态变化时重新绑定滚动监听:

    import { useEffect } from 'react'
    import { useAppState } from '../../store/create'
    
    export default () => {
        const { state: appState } = useAppState()
    
        console.log('appState.items (root)=', appState.items.length)
    
        useEffect(() => {
            const onScroll = () => {
                console.log('appState.items (onScroll)', appState.items.length)
            }
            window.addEventListener('scroll', onScroll)
            return () => window.removeEventListener('scroll', onScroll)
        }, [appState])
    
        return (
            <div className='ItemList'>
                <h1>Hello world</h1>
                {/* 列表项渲染位置 */}
            </div>
        )
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 17:48:16