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

React:类组件转Hook后状态异常,test为何始终为false?

React类组件转Hook后状态更新异常问题

类组件代码及运行表现

export default class myComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { test: false };
    }

    componentDidMount() {
        this.fetchApi();
    }

    fetchApi = () => {
        console.log("fetchApi: " + this.state.test);

        this.setState({ test: !this.state.test }, () => console.log("useEffect: " + this.state.test));
    }

    render() {
        return <div>
            <label>{"test: " + this.state.test}</label>
            <Refresher refresh={this.fetchApi.bind(this)} />
        </div>
    }
}

运行时页面上的test值每次刷新都会在true和false间切换,控制台输出:

fetchApi: false
useEffect: true
fetchApi: true
useEffect: false
fetchApi: false
useEffect: true

Hook代码及运行表现

export default function myHook(props) {
    const [test, setTest] = useState(false);

    let TEST = false;

    useEffect(() => fetchApi(), []);
    useEffect(() => console.log("useEffect: " + test), [test]);

    const fetchApi = () => {
        console.log("fetchApi: test = " + test);
        console.log("fetchApi: TEST = " + TEST);

        setTest(!test);
        TEST = !TEST;
    }

    return <div>
        <label>{"test: " + test}</label>
        <Refresher refresh={fetchApi} />
    </div>
}

运行时页面上的test值始终停留在true,控制台输出:

fetchApi: test = false
fetchApi: TEST = false
useEffect: false
useEffect: true
fetchApi: test = false
fetchApi: TEST = true
fetchApi: test = false
fetchApi: TEST = false

问题

为何Hook中的test变量在fetchApi里始终为false?


补充说明

Refresher组件代码

export default function Refresher(props) {
    const [value, setValue] = useState(0);

    let time = 0;
    let timer = 1;

    useEffect(
        () => {
            timer = props.refreshTimer ?? 5;

            let currentTime = timer > 0 && setInterval(secondEllapsed, 1000);

            return () => clearInterval(currentTime)
        },
        []);

    const secondEllapsed = () => {
        setValue(++time / timer);

        if (time % timer)
            return;

        props.refresh();
    }

    return <>value</>;
}

讨论记录

  • @Andrey Smolko:控制台输出正确,但按钮测试时情况一致,现附上Refresher组件代码
  • @Victor L.:给出的解决方案有效,这和@Slbox提到的依赖项问题、@Konrad提到的陈旧引用是同一回事吗?
  • @Slbox:@Victor L.给出的代码也存在同样的问题吗?
  • @Konrad:好思路,这意味着要结合@Victor L.的方案?

问题原因及解决方案

核心原因:闭包导致的陈旧引用

Hook组件里,fetchApi是初始渲染时创建的函数,它捕获了当时的test状态(false)。而Refresher组件的useEffect依赖是空数组,只会在挂载时执行一次,保存的是初始版本的fetchApi引用。后续每次调用这个fetchApi,它访问的始终是初始的test值,所以setTest(!test)每次都是设置为true,页面状态就停在了true,控制台打印的test自然一直是false。

另外你定义的let TEST = false是组件函数内的普通变量,每次组件重新渲染都会被重新初始化,根本没法保存状态变化,不能替代React的状态管理。

解决方案

方案1:用函数式更新获取最新状态

当状态更新依赖当前状态时,使用setTest的函数式写法,直接拿到最新的状态值,不受闭包影响:

const fetchApi = () => {
    console.log("fetchApi: test = " + test);
    // 函数式更新,prevTest是当前最新状态
    setTest(prevTest => {
        const newTest = !prevTest;
        console.log("useEffect: " + newTest);
        return newTest;
    });
}

方案2:让Refresher获取最新的fetchApi引用

用useCallback包裹fetchApi并添加依赖,同时修改Refresher的useEffect依赖,确保定时器能拿到最新的refresh函数:

修改Hook组件:

export default function myHook(props) {
    const [test, setTest] = useState(false);

    // useCallback包裹,依赖test确保函数更新
    const fetchApi = useCallback(() => {
        console.log("fetchApi: test = " + test);
        setTest(!test);
    }, [test]);

    useEffect(() => fetchApi(), []);
    useEffect(() => console.log("useEffect: " + test), [test]);

    return <div>
        <label>{"test: " + test}</label>
        <Refresher refresh={fetchApi} />
    </div>
}

修改Refresher组件:

export default function Refresher(props) {
    const [value, setValue] = useState(0);
    let time = 0;

    useEffect(
        () => {
            const timer = props.refreshTimer ?? 5;
            time = 0; // 每次重新创建定时器时重置时间
            const currentTime = timer > 0 && setInterval(secondEllapsed, 1000);

            return () => clearInterval(currentTime)
        },
        [props.refresh, props.refreshTimer] // 添加依赖,函数或定时器参数变化时重建定时器
    );

    const secondEllapsed = () => {
        setValue(++time / timer);

        if (time % timer !== 0)
            return;

        props.refresh();
    }

    return <>value</>;
}

方案3:用useRef保存最新函数引用(避免频繁重建定时器)

如果不想每次test变化都重建定时器,可以用useRef保存fetchApi的最新引用,让定时器始终调用最新的函数:

修改Hook组件:

export default function myHook(props) {
    const [test, setTest] = useState(false);
    // 用ref保存最新的fetchApi
    const fetchApiRef = useRef();

    const fetchApi = () => {
        console.log("fetchApi: test = " + test);
        setTest(!test);
    };

    // 每次渲染更新ref的current为最新的fetchApi
    useEffect(() => {
        fetchApiRef.current = fetchApi;
    }, [test]);

    // 封装一个固定引用的函数,内部调用最新的fetchApi
    const wrappedFetchApi = useCallback(() => {
        fetchApiRef.current();
    }, []);

    useEffect(() => wrappedFetchApi(), []);
    useEffect(() => console.log("useEffect: " + test), [test]);

    return <div>
        <label>{"test: " + test}</label>
        <Refresher refresh={wrappedFetchApi} />
    </div>
}

这种方式下Refresher组件不需要修改,因为wrappedFetchApi的引用不会变,它总能通过fetchApiRef.current拿到最新的fetchApi。


内容的提问来源于stack exchange,提问作者ypelissier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 22:21:58