React事件监听中State变量读取旧值的原因及相关疑问
React滚动监听中State旧值问题解析
问题现象
页面加载时通过useState初始化arePostsLoading为true,调用getAllPosts获取数据,完成后将arePostsLoading设为false(页面内容显示、加载状态隐藏,React DevTools也确认该值已更新)。但滚动到底部触发loadMorePosts时,打印的arePostsLoading却仍是初始的true。将arePostsLoading添加到滚动监听的useEffect依赖项后,问题解决,但存在两个疑问:
- 传递给事件监听器的是函数而非变量值,为何会读取旧的state值?
- 如果在
loadMorePosts中嵌套函数打印arePostsLoading,能否获取新值?
核心原因:闭包与渲染快照机制
React组件每次渲染时,组件内的所有变量、函数都会生成当前渲染的快照:
- 第一次渲染时,
arePostsLoading的值是true,此时创建的handleScroll和loadMorePosts函数会通过闭包捕获这个true值。 - 当
arePostsLoading更新为false时,组件会触发新的渲染,生成新的handleScroll和loadMorePosts函数(它们捕获的是最新的false),但旧的滚动事件监听器依然引用第一次渲染时创建的handleScroll函数,所以触发时调用的loadMorePosts还是第一次的快照函数,自然读取到的是旧的true值。
将arePostsLoading加入useEffect依赖项后,每次arePostsLoading变化,useEffect都会重新执行:先移除旧的事件监听器,再添加新的监听器——新监听器引用的是当前渲染生成的handleScroll,该函数捕获的是最新的arePostsLoading值,因此能正确读取状态。
嵌套函数的取值情况
你给出的示例代码中:
function loadMorePosts(){ test1(); } function test1(){ console.log(arePostsLoading); }
test1函数同样会通过闭包捕获它所在渲染周期的arePostsLoading值。如果loadMorePosts是旧渲染快照中的函数,那么test1打印的依然是旧值;只有当loadMorePosts是最新渲染生成的函数时,嵌套的test1才能获取到最新的state。
修正后的关键代码片段
useEffect(() => { function handleScroll() { const scrollHeight = document.documentElement.scrollHeight; const clientHeight = document.documentElement.clientHeight + 2; const scrollTop = window.pageYOffset || document.documentElement.scrollTop; if (scrollHeight - scrollTop < clientHeight) { console.log("End of page reached!"); loadMorePosts(); } } window.addEventListener("scroll", handleScroll); return () => { window.removeEventListener("scroll", handleScroll); }; }, [arePostsLoading, loadMorePosts]); // 实际项目中建议将loadMorePosts加入依赖,或用useCallback包裹避免不必要的重渲染
内容的提问来源于stack exchange,提问作者Ninad Hatankar
相关产品推荐
相关产品推荐

