简体   繁体   English

React 中的 setInterval - 意外行为

[英]setInterval in React - unexpected behavior

I wrote a simple stopwatch in react.我在反应中写了一个简单的秒表。
In order to render new number every second I used setInterval:为了每秒渲染新数字,我使用了 setInterval:

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

const MAX_TIMER = 5;

export default function StopWatch() {
  const [timer, setTimer] = useState(0);
  const tickIntervalId = useRef(null);

  const tickTimer = (getTimerTimeFn) => {
    const number = getTimerTimeFn();

    setTimer(number);

    if(number === MAX_TIMER) {
      clearInterval(tickIntervalId.current);
    }
  }

  let i = 0;
  console.log('i is: ', i) // i value is 0 every render
  const incrementNumber = () => {
    i += 1;
    console.log('in incrementNumber, i is: ', i); // i value is incrementing every render: 1, 2, 3, 4, 5. how?

    return i;
  }

  useEffect(() => {
    tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
  }, [])

  return (
    <div>
      {timer}
    </div>
  );
}

Code Sandbox 代码沙箱

The question is- why is this stopwatch working as expected?问题是 - 为什么这个秒表能按预期工作?
Since every second I re-render the component, the line let i = 0 is executed again, so I expect that the returned value from incrementNumber will always be 1 (0 + 1)由于每一秒我都会重新渲染组件,因此let i = 0行会再次执行,因此我希望incrementNumber的返回值始终为 1 (0 + 1)
But instead, the returned value is incrementing: 1,2,3,4,5.但相反,返回的值是递增的:1,2,3,4,5。
This is the console output:这是控制台 output:

i is:  0
in incrementNumber, i is:  1
i is:  0
in incrementNumber, i is:  2
i is:  0
in incrementNumber, i is:  3
i is:  0
in incrementNumber, i is:  4
i is:  0
in incrementNumber, i is:  5
i is:  0

You need to know about the lexical environment and execution context and how the scope work您需要了解词法环境和执行上下文以及 scope 的工作原理

Already discussed here almost similar example已经在这里讨论过几乎类似的例子
https://stackoverflow.com/a/19123476 https://stackoverflow.com/a/19123476

Here's a little example to demonstrate the lexical scoping issue.这是一个演示词法范围问题的小示例。

function myFn() {
  
  let i = 0; // `i` is local to myFn() (has local scope)
  console.log(`myFn i == ${i}`);
  
  return (caller) => {
    i++;
    console.log(`${caller} : i == ${i}`);
  }
}

let a = myFn(); // 'myFn i == 0'
a('a'); // 'a : i == 1'

let b = myFn(); // 'myFn i == 0'
b('b'); // 'b : i == 1'
b('b'); // 'b : i == 2'
b('b'); // 'b : i == 3'

a('a'); // 'a : i == 2'

@Ido @我愿意

Here's a link to something that makes more sense: I'm using an object so you can see where the variable was stored.这是一个更有意义的链接:我使用的是 object,因此您可以查看变量的存储位置。 I've also upvoted all the other answers as they're correct.我也赞成所有其他答案,因为它们是正确的。 Its a scope issue, not a React issue.它是 scope 问题,而不是 React 问题。

https://codesandbox.io/s/material-demo-wj0pm https://codesandbox.io/s/material-demo-wj0pm

import React, { useEffect, useState, useRef } from "react";

const MAX_TIMER = 5;

let renderCount = 0;
export default function StopWatch() {
  const [timer, setTimer] = useState({});
  const tickIntervalId = useRef(null);

  const tickTimer = getTimerTimeFn => {
    const number = getTimerTimeFn();

    setTimer({...number});

    if (number === MAX_TIMER) {
      clearInterval(tickIntervalId.current);
    }
  };

  var iObject = {
    i: 0,
    inRender: renderCount,
  };
  renderCount++;
  console.log("i is: ", JSON.stringify(iObject) ); // i value is 0 every render
  const incrementNumber = () => {
    iObject.i += 1;
    console.log("in incrementNumber, i is: ", JSON.stringify(iObject)); // i value is incrementing every render: 1, 2, 3, 4, 5. how?
    console.log("------------------------");
    return iObject; //Not a copy
  };

  useEffect(() => {
    tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
  }, []);

  return <div>{JSON.stringify(timer)}</div>;
}

在此处输入图像描述

I believe it is a matter of scope, if you give console.log (i) in useEffect we can see that it only logs in the first rendering.我相信这是scope的问题,如果你在useEffect中给出console.log(i)我们可以看到它只记录第一次渲染。 As if the let i inside the useEffect is different than that of the component.好像 useEffect 里面的 let i 和组件里面的不一样。 Even with the new rendering, the state in useEffect remains.即使使用新的渲染,useEffect 中的 state 仍然存在。

  useEffect(() => {
    console.log("i in effect: ", i)
    tickIntervalId.current = setInterval(tickTimer, 1000, incrementNumber);
  }, []);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM