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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 12:44:53