React中state设置后调用时取值为undefined问题排查
问题原因
你遇到的是React Hooks场景下典型的陈旧闭包问题,和自研下拉组件、fetchBooks内部的请求构造逻辑没有直接关系。
核心逻辑可以拆解为三点:
- React每一次组件渲染都是独立的闭包作用域,每轮渲染对应的state值、内部函数都是独立的——某一轮渲染里定义的函数,默认只能读到当轮渲染对应的state值。
- 你绑定滚动监听的
useEffect依赖数组为空[],这个effect只会在组件首次挂载时执行1次:当时注册到window上的onScroll监听、以及监听函数里引用的fetchBooks,全都是组件首次渲染时生成的旧版本。这个旧版本fetchBooks能读到的isFiction、bookTitle等状态,全是首次渲染时的初始值——你初始化useState的时候没有传默认值,所以这些状态初始就是undefined。 - 后续用户切换下拉选项触发组件重渲染时,state确实更新了,但是因为空依赖的effect不会重新执行,window上绑定的永远是首次挂载时的旧监听函数,闭包里存的永远是第一次渲染时的旧state,自然读不到更新后的
isFiction值。
你写的另一个监听isFiction的useEffect能正常拉取数据,是因为它把isFiction放在了依赖数组里:每次isFiction变化,effect就会重新执行,拿到的是当前最新渲染轮次里的fetchBooks,自然能读到正确的状态值。
另外你的代码还有一个隐藏问题:现在用普通变量存储的searchOffset不属于React状态,组件重渲染时这个值会被重置,后续滚动分页时会出现偏移量错乱的问题,建议存为state或useRef。
低改造成本修复方案
两种方案选其一即可,不需要重构现有业务逻辑:
方案1:通过ref挂载最新函数引用(改造成本最低)
不需要调整现有fetchBooks的实现,只需要加一个ref中转最新的函数引用,保证滚动监听永远能拿到最新版本的fetchBooks:
// 组件内新增ref定义 const fetchBooksRef = useRef(); // 每次渲染都把当前最新的fetchBooks挂载到ref上 fetchBooksRef.current = fetchBooks; // 滚动监听的逻辑只需要改调用方式,依赖保持空数组即可 useEffect(() => { const onScroll = function () { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { // 从ref上读取最新版本的函数调用 fetchBooksRef.current(true) } } window.addEventListener('scroll', onScroll) return () => window.removeEventListener('scroll', onScroll) }, [])
这个方案不需要频繁解绑重绑滚动事件,没有额外性能开销。
方案2:补全依赖配合useCallback缓存函数
如果你更倾向于遵循React Hooks的依赖规范,可以用useCallback缓存fetchBooks,同时补全滚动effect的依赖:
// 用useCallback包裹fetchBooks,把内部用到的所有state都加入依赖数组 const fetchBooks = useCallback((updating=false) => { // 此处保留你原有的fetchBooks全部逻辑 // 注意记得把searchOffset改为state或useRef存储,避免重渲染时值丢失 }, [isFiction, bookTitle /* 其余fetchBooks用到的state都加在这里 */]) // 滚动effect的依赖中加入fetchBooks useEffect(() => { const onScroll = function () { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { fetchBooks(true) } } window.addEventListener('scroll', onScroll) return () => window.removeEventListener('scroll', onScroll) }, [fetchBooks])
这个方案的逻辑是:只有当fetchBooks依赖的筛选状态变化时,fetchBooks才会更新,进而触发滚动监听的重新绑定,保证绑上去的函数永远能读到最新的状态值。
内容的提问来源于stack exchange,提问作者dsp_099
相关产品推荐
相关产品推荐

