【问题标题】:setInterval in React - unexpected behaviorReact 中的 setInterval - 意外行为
【发布时间】:2020-06-16 15:54:46
【问题描述】:

我在 react 中写了一个简单的秒表。
为了每秒渲染新的数字,我使用了 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

问题是 - 为什么这款秒表能按预期工作?
由于每一秒我都会重新渲染组件,所以let i = 0 行会再次执行,所以我希望 incrementNumber 的返回值将始终为 1 (0 + 1)
但相反,返回的值是递增的:1,2,3,4,5。
这是控制台输出:

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

【问题讨论】:

  • iincrementNumber 声明移动到全局范围。
  • 代码正在运行,您可以从我附加的代码沙箱中看到。问题不在于如何让它发挥作用。
  • @Ido 您在函数范围之外声明变量i,因此每次调用它都会递增。如果您在函数incrementNumber 中声明i,它将始终重新初始化为零。那是你的问题吗? i 实际上是 StopWatch() 的成员
  • @DanielB.Chapman 这不是问题,问题是i 怎么可能在incrementNumber 函数内不断增加,即使i 没有存储在状态中,而你甚至可以在控制台输出中看到 i 在每次渲染时都设置为 0。
  • @Ido 这是一个词法作用域问题,这就是为什么将 iincrementNumber 移动到全局作用域会破坏它。

标签: javascript reactjs setinterval


【解决方案1】:

@Ido

这里有一个更有意义的链接:我正在使用一个对象,以便您可以看到变量的存储位置。我也赞成所有其他答案,因为它们是正确的。这是一个范围问题,而不是 React 问题。

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>;
}

【讨论】:

    【解决方案2】:

    您需要了解词法环境和执行上下文以及作用域的工作原理

    已经在这里讨论过了 几乎相似的例子
    https://stackoverflow.com/a/19123476

    【讨论】:

      【解决方案3】:

      这里有一个小例子来演示词法作用域问题。

      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'
      

      【讨论】:

        【解决方案4】:

        我相信这是一个范围问题,如果你在 useEffect 中给出 console.log (i) 我们可以看到它只记录第一次渲染。好像 useEffect 里面的 let i 和组件里面的不一样。即使使用新的渲染,useEffect 中的状态仍然存在。

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

        【讨论】:

        • 感谢您的回答。但是之所以只打印一次,是因为这个useEffect只在组件挂载时运行一次(因为我提供的空数组,就像ComponentDidMount)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-08-27
        • 2021-07-29
        • 2021-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多