使用React钩子实现自增量计数器



代码如下:https://codesandbox.io/s/nw4jym4n0

export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
});
return <h1>{counter}</h1>;
};

问题是每个setCounter触发器都会重新渲染,因此间隔被重置并重新创建。这看起来可能很好,因为状态(计数器(一直在增加,但当与其他挂钩结合时,它可能会冻结。

正确的方法是什么?在类组件中,通过一个实例变量来保持间隔很简单。

可以给useEffect一个空数组作为第二个参数,这样函数在初始渲染后只运行一次。由于闭包的工作方式,这将使counter变量始终引用初始值。然后,您可以使用setCounter的函数版本来始终获得正确的值。

示例

const { useState, useEffect } = React;
function App() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>

一种更通用的方法是创建一个新的自定义钩子,该钩子将函数存储在ref中,并且只有在delay发生变化时才创建新的区间,就像Dan Abramov在他的伟大博客文章"strong"中所做的那样;使用React Hooks使setInterval声明"。

示例

const { useState, useEffect, useRef } = React;
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
let id = setInterval(() => {
savedCallback.current();
}, delay);
return () => clearInterval(id);
}, [delay]);
}
function App() {
const [counter, setCounter] = useState(0);
useInterval(() => {
setCounter(counter + 1);
}, 1000);
return <h1>{counter}</h1>;
};
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="root"></div>

@YangshunTay的另一个答案已经显示,可以使其useEffect回调只运行一次,并且与componentDidMount类似。在这种情况下,由于函数作用域的限制,有必要使用状态更新器函数,否则更新的counter将无法在setInterval回调中使用。

另一种选择是在每次计数器更新时运行useEffect回调。在这种情况下,setInterval应替换为setTimeout,更新应限于counter更新:

export default ({ name }: Props) => {
const [counter, setCounter] = useState(0);
useEffect(() => {
const timeout = setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimeout(timeout);
};
}, [counter]);
return <h1>{counter}</h1>;
};

正确的方法是只运行一次效果。由于您只需要运行一次效果,因为在挂载过程中,您可以传入一个空数组作为第二个参数来实现。

但是,您需要更改setCounter才能使用以前的counter值。原因是传递到setInterval的闭包中的回调在第一次渲染中只访问counter变量,在随后的渲染中无法访问新的counter值,因为第二次没有调用useEffect();CCD_ 21在CCD_。

与您熟悉的setState一样,状态挂钩有两种形式:一种是接受更新状态的形式,另一种是传入当前状态的回调形式。您应该使用第二种形式,并在setState回调中读取最新的状态值,以确保您在递增状态值之前拥有最新状态值。

function Counter() {
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
const timer = setInterval(() => {
setCounter(prevCount => prevCount + 1); // <-- Change this line!
}, 1000);
return () => {
clearInterval(timer);
};
}, []); // Pass in empty array to run effect only once!
return (
<div>Count: {counter}</div>
);
}
ReactDOM.render(<Counter />, document.querySelector('#app'));
<script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>

相关内容

  • 没有找到相关文章

最新更新