setInterval в React - неожиданное поведение - PullRequest
0 голосов
/ 16 июня 2020

Я написал простой секундомер в 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

Ответы [ 4 ]

2 голосов
/ 17 июня 2020

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

enter image description here

2 голосов
/ 16 июня 2020

Вот небольшой пример, демонстрирующий проблему лексической области видимости.

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'
2 голосов
/ 16 июня 2020

Вам нужно знать о лексической среде и контексте выполнения и о том, как работает область видимости

Уже обсужденный здесь почти аналогичный пример
{ ссылка }

1 голос
/ 16 июня 2020

Я считаю, что это вопрос объема, если вы дадите console.log (i) в useEffect, мы увидим, что он регистрируется только при первом рендеринге. Как будто let i внутри useEffect отличается от такового в компоненте. Даже при новом рендеринге состояние useEffect остается.

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