使用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;
上面的代码是一个计数器组件,它需要用props进行计数,然后将inc初始化为0,并将其递增,直到等于count为止。
问题是,每次我获得0时,我都无法在useEffect和setInterval的回调中获得inc的更新值,因此它将inc呈现为1,而setInterval却永远无法清除。我认为inc必须在use useeffect和setInterval的回调函数的结尾,所以我必须在那儿获取update inc,所以也许是一个错误?
我不能在依赖关系中传递inc(在其他类似的问题中建议),因为在我的情况下,我在useEffect中设置了setInterval,因此在依赖关系数组中传递inc会导致无限循环
我有一个使用有状态组件的可行解决方案,但我想使用功能组件来实现这一点
答案 0 :(得分:1)
这里的问题是,clearInterval
每次运行时(即useEffect
更新时)都定义了count
的回调。定义时,inc
的值是将在回调中读取的值。
此编辑使用不同的方法。我们提供了一个引用来跟踪inc
小于count
,如果小于,我们可以继续递增inc
。如果不是,那么我们清除计数器(就像您在问题中一样)。每次inc
更新时,我们都会评估它是否仍小于计数并将其保存在ref
中。然后,该值将在前一个useEffect
中使用。
我包含了对speed
的依赖,就像@DennisVash在他的回答中正确指出的那样。
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;
};
答案 1 :(得分:1)
有两个问题:
useEffect
返回函数来清除间隔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;
}
另一种选择是将inc
包含在deps数组中,这使事情变得更加简单,因为您不需要在inc
中使用上一个setInc
:
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
,请在调用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;
答案 2 :(得分:0)
需要处理的主要问题是Closures和在取决于道具的条件下的清理间隔。
您应该在功能setState
中添加条件检查:
setInc(inc => (inc < count ? inc + 1 : inc));
此外,清除间隔应该在卸载时发生。
如果要在条件(clearInterval
)上添加inc < count
,则需要保存间隔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'));