Preact函数组件中useState变量无法获取最新值的问题与解决
为什么Preact滚动事件里拿不到更新后的state值?怎么解决?
先看你提供的代码:
import { useState, useEffect } from "preact/hooks"; export function App() { const [pageContent, setPageContent] = useState(""); useEffect(() => { setPageContent("main"); window.addEventListener("scroll", handleScroll); }, []); useEffect(() => { console.log(222, pageContent); // main }, [pageContent]); const handleScroll = () => { console.log(333, pageContent); // "" }; return ( <> <p>enough content to make the page scroll</p> </> ) }
问题原因
这是闭包捕获固定值导致的:
- 第一个
useEffect依赖是空数组,只会在组件首次挂载时执行,此时绑定的handleScroll函数,捕获的是组件第一次渲染时的pageContent(初始空字符串)。 - 当你调用
setPageContent("main")更新state后,组件会重新渲染,生成新的handleScroll函数(这个新函数能拿到最新的pageContent),但window上绑定的还是旧的handleScroll,它内部的pageContent始终是初始的空值,所以打印结果不符合预期。
解决方案
方案1:state更新时重新绑定事件
把pageContent加入第一个useEffect的依赖数组,每次state变化时,先移除旧的事件监听,再绑定新的处理函数:
import { useState, useEffect } from "preact/hooks"; export function App() { const [pageContent, setPageContent] = useState(""); useEffect(() => { // 首次挂载时设置初始值,后续state更新时不需要重复设置 if (!pageContent) { setPageContent("main"); } const handleScroll = () => { console.log(333, pageContent); }; window.addEventListener("scroll", handleScroll); // 清理函数:组件卸载或依赖变化时移除监听 return () => window.removeEventListener("scroll", handleScroll); }, [pageContent]); useEffect(() => { console.log(222, pageContent); // main }, [pageContent]); return ( <> <p>enough content to make the page scroll</p> </> ) }
这样每次pageContent更新,都会生成新的handleScroll并重新绑定,确保事件触发时拿到最新的state值。
方案2:用useRef保存最新state值
利用useRef的current属性可变的特性,同步保存pageContent的最新值,事件处理函数直接取ref的current值:
import { useState, useEffect, useRef } from "preact/hooks"; export function App() { const [pageContent, setPageContent] = useState(""); const pageContentRef = useRef(pageContent); // 每次state更新,同步到ref中 useEffect(() => { pageContentRef.current = pageContent; }, [pageContent]); useEffect(() => { setPageContent("main"); const handleScroll = () => { console.log(333, pageContentRef.current); // 取最新值 }; window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, []); useEffect(() => { console.log(222, pageContent); // main }, [pageContent]); return ( <> <p>enough content to make the page scroll</p> </> ) }
这种方式不需要频繁解绑/绑定事件,适合state更新频繁的场景。
方案3:用useCallback包裹事件函数
结合useCallback管理事件函数的更新,确保handleScroll在pageContent变化时生成新函数,再配合useEffect重新绑定:
import { useState, useEffect, useCallback } from "preact/hooks"; export function App() { const [pageContent, setPageContent] = useState(""); // 依赖pageContent,state变化时生成新的handleScroll const handleScroll = useCallback(() => { console.log(333, pageContent); }, [pageContent]); useEffect(() => { setPageContent("main"); window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, [handleScroll]); // 依赖handleScroll,函数更新时重新绑定 useEffect(() => { console.log(222, pageContent); // main }, [pageContent]); return ( <> <p>enough content to make the page scroll</p> </> ) }
内容的提问来源于stack exchange,提问作者nathan work
相关产品推荐
相关产品推荐

