useState 中的变量未在 useEffect 回调中更新

variable in useState not updating in useEffect callback

我在使用 useState 和 useEffect 挂钩时遇到问题

import { useState, useEffect } from "react";

const counter = ({ count, speed }) => {
    const [inc, setInc] = useState(0);

    useEffect(() => {

        const counterInterval = setInterval(() => {
            if(inc < count){
                setInc(inc + 1);
            }else{
                clearInterval(counterInterval);
            }
        }, speed);

    }, [count]);

    return inc;
}

export default counter;

上面的代码是一个计数器组件,它把count传入props,然后用0初始化inc并自增直到等于count

问题是每次我得到 0 时,我都没有在 useEffect 和 setInterval 的回调中得到 inc 的更新值,所以它将 inc 呈现为 1 并且 setInterval 永远不会变得清晰。我认为 inc 必须关闭使用 useEffect 和 setInterval 的回调,所以我必须在那里获取更新 inc,所以这可能是一个错误?

我不能在依赖项中传递 inc(在其他类似问题中建议),因为在我的例子中,我在 useEffect 中设置了 setInterval,因此在依赖项数组中传递 inc 会导致无限循环

我有一个使用有状态组件的工作解决方案,但我想使用功能组件来实现这个

这里的问题是每次 useEffect 运行时都会定义来自 clearInterval 的回调,也就是 count 更新时。定义时的值 inc 是将在回调中读取的值。

这次编辑采用了不同的方法。我们包含一个 ref 来跟踪 inc 是否小于 count,如果小于,我们可以继续递增 inc。如果不是,那么我们会清除计数器(就像您在问题中所做的那样)。每次inc更新,我们评估它是否仍然小于count并保存在ref中。然后在前面的useEffect.

中使用这个值

正如@DennisVash 在他的回答中正确指出的那样,我包含了对 speed 的依赖。

const useCounter = ({ count, speed }) => {
    const [inc, setInc] = useState(0);
    const inc_lt_count = useRef(inc < count);

    useEffect(() => {
        const counterInterval = setInterval(() => {
            if (inc_lt_count.current) {
                setInc(inc => inc + 1);
            } else {
                clearInterval(counterInterval);
            }
        }, speed);

        return () => clearInterval(counterInterval);
    }, [count, speed]);

    useEffect(() => {
        if (inc < count) {
            inc_lt_count.current = true;
        } else {
            inc_lt_count.current = false;
        }
    }, [inc, count]);

    return inc;
};

主要需要解决的问题是Closures和依赖道具的条件清关间隔

您应该在功能 setState:

中添加条件检查
setInc(inc => (inc < count ? inc + 1 : inc));

此外,清除间隔应该发生在卸载时。

如果要在条件(inc < count)上添加clearInterval,需要保存间隔id和增加的数字的引用:

import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';

const useCounter = ({ count, speed }) => {
  const [inc, setInc] = useState(0);

  const incRef = useRef(inc);
  const idRef = useRef();

  useEffect(() => {
    idRef.current = setInterval(() => {
      setInc(inc => (inc < count ? inc + 1 : inc));
      incRef.current++;
    }, speed);

    return () => clearInterval(idRef.current);
  }, [count, speed]);

  useEffect(() => {
    if (incRef.current > count) {
      clearInterval(idRef.current);
    }
  }, [count]);

  useEffect(() => {
    console.log(incRef.current);
  });

  return inc;
};

const App = () => {
  const inc = useCounter({ count: 10, speed: 1000 });
  return <h1>Counter : {inc}</h1>;
};

ReactDOM.render(<App />, document.getElementById('root'));

有几个问题:

  1. 您没有return使用useEffect中的函数来清除间隔
  2. 您的 inc 值不同步,因为您没有使用 inc 的先前值。

一个选项:

const counter = ({ count, speed }) => {
    const [inc, setInc] = useState(0);

    useEffect(() => {
        const counterInterval = setInterval(() => {
            setInc(inc => {
                if(inc < count){
                    return inc + 1;
                }else{
                    // Make sure to clear the interval in the else case, or 
                    // it will keep running (even though you don't see it)
                    clearInterval(counterInterval);
                    return inc;
                }
            });
        }, speed);

        // Clear the interval every time `useEffect` runs
        return () => clearInterval(counterInterval);

    }, [count, speed]);

    return inc;
}

另一种选择是在 deps 数组中包含 inc,这使事情变得更简单,因为您不需要在 setInc:[=22= 中使用之前的 inc ]

const counter = ({ count, speed }) => {
    const [inc, setInc] = useState(0);

    useEffect(() => {
        const counterInterval = setInterval(() => {
            if(inc < count){
                return setInc(inc + 1);
            }else{
                // Make sure to clear your interval in the else case,
                // or it will keep running (even though you don't see it)
                clearInterval(counterInterval);
            }
        }, speed);

        // Clear the interval every time `useEffect` runs
        return () => clearInterval(counterInterval);

    }, [count, speed, inc]);

    return inc;
}

还有第三种方法更简单: 在 deps 数组中包含 inc,如果 inc >= count、return 在调用 setInterval 之前尽早包含:

    const [inc, setInc] = useState(0);

    useEffect(() => {
        if (inc >= count) return;

        const counterInterval = setInterval(() => {
          setInc(inc + 1);
        }, speed);

        return () => clearInterval(counterInterval);
    }, [count, speed, inc]);

    return inc;