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

React中定时器调用set方法时useState状态不更新问题排查

问题:React中useState变量无法正确更新,setHash看似未生效

我有一个WebSocket服务器,每15秒发送一个包含哈希值的对象。客户端收到哈希后,需要与当前存储的哈希对比,若不同则调用API获取新数据。WebSocket功能正常,能正确发送哈希,当服务器数据更新时我会收到不同的哈希值,但存储当前哈希的hash变量无法正确更新。

为排查问题,我禁用了组件中的WebSocket监听,改用setInterval模拟更新,代码如下:

import { useCallback, useEffect, useState } from "react";
import { useAuth, useSocket } from "../utils/hooks";

const Admin = () => {
    const [ questionLists, setQuestionLists ] = useState<QuestionListModel[]>([]); 
    const { user } = useAuth();
    const { socket } = useSocket();
    const [ hash, setHash ] = useState<Hash>({questionList: ""});

    const fetchHash = useCallback(async () => {
        setHash({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
    }, []);

    const fetchQuestionLists = useCallback(async () => {
        console.log("fetching new question lists");
        const response: ApiResponse | boolean = await getQuestionLists(user?.token);

        if (typeof response !== "boolean" && response.data) {
            setQuestionLists(response.data);
        }
    }, [hash]);

    useEffect(() => {    
        fetchHash();
        fetchQuestionLists();
    }, []);

    const update = useCallback((newHash: Hash) => {
        console.log("called update");

        let shouldUpdate = false;
        let originalHash = { ...hash };
        let updatedHash = { ...newHash };

        console.log("new:    ", newHash);
        console.log("stored: ", originalHash);

        if (hash.questionList !== newHash.questionList) {
            console.log("was not equal");
            updatedHash = { ...updatedHash, questionList: newHash.questionList} 
            shouldUpdate = true;
        }

        if (shouldUpdate) {
            console.log("trying to set new hash: ", updatedHash);
            setHash(updatedHash);
            fetchQuestionLists();
        }
    }, [hash]);

    /*useEffect(() => {    
        socket?.on('aHash', (fetchedHash) => update(fetchedHash));
    }, []);*/

    useEffect(() => {    
        setInterval(() => {
            update({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
        }, 15000)
    }, []);

    return (
        <>
            ... Things here later ...
        </>
    );
};

export default Admin;

初始渲染后等待两个定时器周期,控制台输出如下:

fetching new question lists
called update
new:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
stored:  {questionList: ''}
was not equal
trying to set new hash:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
fetching new question lists
called update
new:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
stored:  {questionList: ''}
was not equal
trying to set new hash:  {questionList: 'sdhfubvwuedfhvfeuvyqhwvfeuq'}
fetching new question lists

可以看到stored的哈希值为空,似乎setHash(updatedHash)未生效,请问这是什么原因?

原因分析与解决方案

核心原因:闭包导致的过期状态(stale state)

  • update函数通过useCallback包裹,依赖数组为[hash],但启动setInterval的useEffect未将update加入依赖,导致定时器调用的update始终是组件首次渲染时的版本,该版本捕获的hash是初始值{questionList: ""},后续setHash更新状态后,旧的update函数内的hash不会同步更新。
  • 注释掉的WebSocket监听useEffect同样未依赖update,也会导致监听回调调用的是旧版本update,捕获初始hash值。

具体解决方案

1. 用函数式更新获取最新状态,修复update闭包问题

不要直接依赖外部hash,而是通过setHash的函数式更新参数获取最新状态,同时调整useCallback依赖:

const update = useCallback((newHash: Hash) => {
    console.log("called update");

    setHash(prevHash => {
        let shouldUpdate = false;
        let updatedHash = { ...prevHash };

        console.log("new:    ", newHash);
        console.log("stored: ", prevHash);

        if (prevHash.questionList !== newHash.questionList) {
            console.log("was not equal");
            updatedHash.questionList = newHash.questionList;
            shouldUpdate = true;
        }

        if (shouldUpdate) {
            console.log("trying to set new hash: ", updatedHash);
            fetchQuestionLists();
            return updatedHash;
        }
        return prevHash;
    });
}, [fetchQuestionLists]);

2. 修复定时器与WebSocket监听的依赖,添加清理逻辑

启动setInterval的useEffect需加入update依赖,同时清除定时器避免内存泄漏:

useEffect(() => {    
    const intervalId = setInterval(() => {
        update({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
    }, 15000);
    
    return () => clearInterval(intervalId);
}, [update]);

恢复WebSocket监听时,同样加入update依赖,并在组件卸载时移除监听:

useEffect(() => {    
    if (!socket) return;
    
    const handleHash = (fetchedHash) => update(fetchedHash);
    socket.on('aHash', handleHash);
    
    return () => socket.off('aHash', handleHash);
}, [socket, update]);

3. 优化fetchQuestionLists的依赖

fetchQuestionLists无需依赖hash,因为它会在哈希更新后主动调用,只需保留user?.token作为依赖:

const fetchQuestionLists = useCallback(async () => {
    console.log("fetching new question lists");
    if (!user?.token) return;
    const response: ApiResponse | boolean = await getQuestionLists(user.token);

    if (typeof response !== "boolean" && response.data) {
        setQuestionLists(response.data);
    }
}, [user?.token]);

4. 简化初始化逻辑

初始useEffect可直接调用update完成初始化,无需单独fetchHash:

useEffect(() => {    
    update({questionList: "sdhfubvwuedfhvfeuvyqhwvfeuq"});
}, [update]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 11:05:31