在ReactJs中,setInterval内部的函数不正常工作。

huangapple go评论65阅读模式
英文:

function inside setInterval does not work properly in ReactJs

问题

我已经看到了很多关于在React中创建定时器的教程,通过以下方式进行操作:

useEffect(() => {
  let interval = null;
  if (timeractive) {
    interval = setInterval(() => {
      setCount(count => count + 1);
    }, 50);
  } else {
    clearInterval(interval);
  }
  return () => clearInterval(interval);
}, [timeractive, count]);

这是我认为发生的事情:

  1. timeractive = true
  2. 调用useEffect,将变量interval分配给setInterval
  3. 调用setCount,增加count的值。
  4. 再次调用useEffect,因为count是其依赖之一。但是前一个useEffect的清理函数首先运行,清除了变量interval
  5. 回到步骤2

我不想以这种方式做事,因为它似乎不太直观。实际上,它是通过创建和清除一堆仅持续一次迭代的小间隔来创建一个大的“interval”。

因此,我编写了以下代码,尝试创建一个不依赖于正在递增的内容的定时器。这就是我决定使用useRef的原因,以便间隔在渲染之间保持持久。

这是一个每100ms增加半径并绘制具有该半径(模50)的圆的计时器(应该是动画,但不起作用)。但是半径似乎更新正常。因此,drawcircle函数存在一些问题。

我在这种情况下是否可以有人解释闭包的概念?

const { useState, useEffect, useRef } = React;

function Timer({ active }) {
  const intervalRef = useRef(null);
  const canvasRef = useRef(null);
  const [width, height] = [500, 500];
  const [radius, setRadius] = useState(30);
  useEffect(() => {
    if (active) {
      intervalRef.current = setInterval(() => {
        drawcircle();
        setRadius(radius => radius + 1);
      }, 100);
    } else {
      clearInterval(intervalRef.current);
    }
  }, [active]);
  const drawcircle = () => {
    const context = canvasRef.current.getContext('2d');
    context.clearRect(0, 0, width, height);
    context.beginPath();
    context.arc(width / 2, height / 2, radius % 50, 0, 2 * Math.PI);
    context.stroke();
  };
  return (
    <div>
      <canvas ref={canvasRef} width={width} height={height} />
      <p>半径是 {radius}</p>
    </div>
  );
}

function Main() {
  const [active, setActive] = useState(false);
  return (
    <div>
      <Timer active={active} />
      <button onClick={() => { setActive(!active) }}>切换</button>
    </div>
  );
}

ReactDOM.render(<Main />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="app"></div>

希望这能帮助你理解代码中发生的事情。

英文:

I have seen many tutorials on creating timers in React by doing something like this

  useEffect(() =&gt; {
let interval = null;
if (timeractive) {
interval = setInterval(() =&gt; {
setCount(count =&gt; count+ 1);
}, 50);
} else {
clearInterval(interval);
}
return () =&gt; clearInterval(interval);
},[timeractive,count]);

This is what I think is happening:

1 timeractive = true

2 useEffect gets called, declaring assigning the variable interval to a setInterval

3 setCount gets called, incrementing count.

4 useEffect gets called again since count is one of its dependencies. But the cleanup function from the previous useEffect runs first, clearing the variable interval

5 go back to step 2

I don't want to do things this way because it seems quite unintuitive. It is essentially creating a large "interval" by creating and clearing a bunch of smaller intervals that only lasts one iteration.

So I wrote the following code, trying to create a timer that does not depend on what's being incremented. That why I decided to use useRef so that the interval will be persist between renders.

This is a timer that increases the radius every 100ms and draw a circle with that radius (modulo 50), it is supposed to be an animation but it doesn't work. However the radius seems to be updating fine. So there is something going on with the drawcircle function

I asked a similar question here, that's why I decided to pass down an updater function in setRadius

Can someone explain the concept of closure in this case?

<!-- begin snippet: js hide: false console: true babel: true -->

<!-- language: lang-js -->

const {useState,useEffect,useRef} = React;
function Timer({active}) {
const intervalRef = useRef(null);
const canvasRef = useRef(null);
const [width,height] = [500,500];
const [radius, setRadius] = useState(30);
useEffect(()=&gt;{
if(active){
intervalRef.current = setInterval(()=&gt;{
drawcircle();
setRadius(radius =&gt; radius + 1);
},100)
} else {
clearInterval(intervalRef.current) 
}
},[active])
const drawcircle = ()=&gt;{
const context = canvasRef.current.getContext(&#39;2d&#39;);
context.clearRect(0,0,width,height)
context.beginPath()
context.arc(width/2,height/2,radius%50,0,2*Math.PI)
context.stroke();
}
return (
&lt;div&gt;
&lt;canvas ref={canvasRef} width={width} height={height}/&gt;
&lt;p&gt;radius is {radius}&lt;/p&gt;
&lt;/div&gt;
)
}
function Main() {
const [active, setActive] = useState(false)
return (
&lt;div&gt;
&lt;Timer active={active}/&gt;
&lt;button onClick={()=&gt;{setActive(!active)}}&gt;Toggle&lt;/button&gt;
&lt;/div&gt;
)
}
ReactDOM.render(&lt;Main /&gt;, document.getElementById(&#39;app&#39;));

<!-- language: lang-html -->

&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js&quot;&gt;&lt;/script&gt;
&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;

<!-- end snippet -->

答案1

得分: 1

The interval callback uses the drawcircle method of the first render on each iteration, and that drawcircle method refers to the initial radius value. To solve this, use a ref for the drawcircle method.

drawcircleRef = useRef()
useEffect(() => {
    if (active) {
        drawcircleRef.current = drawcircle
        const interval = setInterval(() => {
            drawcircleRef.current();
            setRadius(radius => radius + 1);
        }, 100)
        return () => clearInterval(interval) 
    }
}, [active])
useEffect(() => {
    drawcircleRef.current = drawcircle
}, [radius])
英文:

The interval callback use the drawcircle method of the first render on each iteration and that drawcircle method refers to the initial radius value. To solve this, use a ref to drawcircle method

drawcircleRef = useRef()
useEffect(()=&gt;{
if(active){
drawcircleRef.current = drawcircle
const interval= setInterval(()=&gt;{
drawcircleRef.current();
setRadius(radius =&gt; radius + 1);
},100)
return ()=&gt;  clearInterval(interval) 
}
},[active])
useEffect(()=&gt;{
drawcircleRef.current = drawcircle
},[radius])

huangapple
  • 本文由 发表于 2020年1月4日 11:56:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/59587750.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定