状态在 useEffect 中没有更新,为啥?

Posted

技术标签:

【中文标题】状态在 useEffect 中没有更新,为啥?【英文标题】:State is not updating in useEffect why?状态在 useEffect 中没有更新,为什么? 【发布时间】:2022-01-16 17:55:00 【问题描述】:

我正在尝试制作一个加密价格跟踪网站。我在每 10 秒更新一次 coinList 变量 prevData 变量后获取数据。我也想访问 useEffect 中的 prevData 变量,但在其中变量没有更新,它的值仍然是 []。但是,如果我在 useEffect 之外使用 prevData,数据会发生变化,我可以使用它。

我只想要 prevData 在 useEffect 中更新的值。

function App() 
    const [coinsList, setCoinsList] = useState([]);
    const [prevData, setPrevData] = useState([]);
    let [width, setWidth] = useState(window.innerWidth)
    useEffect(() => 
        async function fetchData() 
            const response = await fetch('api key').then(res => res.json())
setCoinsList(res);
setPrevData(res);
        
        fetchData()
        async function fetchData2() 
            const res = await fetch('api key').then(res => res.json())
            for (let i = 0; i < prevData.length; i++) 
                if (res[i].current_price > prevData[i].current_price) 
                    let coin = document.getElementById(`$prevData[i].id`)
                    coin.classList.add('green')
                    console.log(coin)
                    // setTimeout(() => 
                    //     coin.classList.remove('green')
                    // , 1000)
                 else if (res[i].current_price < prevData[i].current_price) 
                    let coin = document.getElementById(`$prevData[i].id`)
                    coin.classList.add('red')
                    console.log(coin)

                    // setTimeout(() => 
                    //     coin.classList.remove('red')
                    // , 1000)
                 else 
                    let coin = document.getElementById(`$prevData[i].id`)
                    console.log(coin)
                    coin.classList.add('blue')
                    // setTimeout(() => 
                    //     coin.classList.remove('blue')
                    // , 1000)
                
            
            setPrevData(res);
            setCoinsList(res);
        
        setInterval(() => 
            fetchData2()
            console.log(prevData) // it is not updating
        , 10000)
    , [])
    useEffect(() => 
        window.addEventListener('resize', () => 
            setWidth(window.innerWidth)
        )
    , [window.innerWidth])
    console.log(prevData) // it is updating 

【问题讨论】:

【参考方案1】:

fetchData2 中,您拥有prevData 状态的陈旧外壳。它在区间回调范围内被关闭。

一种补救方法是使用 React ref 和一个额外的 useEffect 挂钩来缓存可以在 fetchData2 回调中访问的 prevData 状态的副本。

function App() 
  const [coinsList, setCoinsList] = useState([]);
  const [prevData, setPrevData] = useState([]);

  const prevDataRef = React.useRef(prevData);

  useEffect(() => 
    prevDataRef.current = prevData;
  , [prevData]);

  ...

  useEffect(() => 
    ...
    async function fetchData2() 
      const prevData = prevDataRef.current; // <-- grab current value

      const res = await fetch('api key').then(res => res.json());

      for (let i = 0; i < prevData.length; i++) 
        if (res[i].current_price > prevData[i].current_price) 
          let coin = document.getElementById(`$prevData[i].id`);
          coin.classList.add('green');
         else if (res[i].current_price < prevData[i].current_price) 
          let coin = document.getElementById(`$prevData[i].id`);
          coin.classList.add('red');
         else 
          let coin = document.getElementById(`$prevData[i].id`);
          coin.classList.add('blue');
        
      
      setPrevData(res);
      setCoinsList(res);
    

    setInterval(() => 
      fetchData2();
    , 10000)
, []);

【讨论】:

感谢您的帮助 现在我的代码可以按照我的意愿完美运行。但是你能解释一下为什么它不起作用吗?我没有得到 prevData 正在关闭,它在 setinterval 上关闭 - @Nimish 正确,初始 prevData 值在 setInterval 回调范围内被关闭。这意味着您创建一个匿名回调函数并将其传递给setInterval。这是 that 函数的特定副本或“实例”,其中复制了所有使用的变量。此间隔回调的相同副本/实例被重复使用且永不更新,因此内部复制的变量也永不更新。 React ref 是一个外部(回调范围)可变的对象。 这意味着我们在setInterval回调函数中添加的任何东西,无论它当时的状态是什么,它只会重复那个状态,不会根据原来的状态更新? @Nimish Affirmative.【参考方案2】:

您在setInterval 范围内的prevData 值上有closure,您可以使用引用来修复它:

const prevDataRef = useRef();

// Keep the ref updates
useEffect(() => 
  prevDataRef.current = prevData;
, []);

// no closure
useEffect(() => 
  setInterval(() => 
    fetchData2(); // use prevDataRef.current
    console.log(prevDataRef.current); // updated
  , 10000);
, []);

【讨论】:

以上是关于状态在 useEffect 中没有更新,为啥?的主要内容,如果未能解决你的问题,请参考以下文章

状态更新没有立即反映。如何使用 useEffect 的依赖数组来显示更改?

React useEffect 清理函数中的依赖没有更新

为啥 useEffect 不能在 return 语句中访问我的状态变量?

为啥 useState 钩子在 useEffect() 中使用循环时不更新

React:“无法在未安装的组件上执行 React 状态更新”,没有 useEffect 功能

在 useEffect 中使用 useState 值而不使状态更新 useEffect