函数组件内部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

