В обработчике событий React, Как безопасно сделать несколько вызовов setState? - PullRequest
0 голосов
/ 26 апреля 2020

У меня есть следующий код:

    function App() {

      // array to store 3 numbers, all initialized to 0
      const [counter, setCounter] = React.useState<number[]>([0, 0, 0]);

      // increments counter[i] by 1
      const increment = (i: number) => {
        let newCount = [...counter];
        newCount[i] += 1;  
        setCounter(newCount);
      }

      return (
        <div className="App">
          <header className="App-header">
            <p>            
              count: {counter[0]} {counter[1]} {counter[2]}
            </p>
            <button onClick={() => {
              [0, 1, 2].map((i) => {  
                // pretend that setTimeout is making some web request
                setTimeout(() => increment(i), 1000); 
              });
            }} >Increment All</button>
          </header>
        </div>
      );
    }

Когда я нажимаю на кнопку, я хочу, чтобы она увеличивала все 3 числа в массиве, но похоже, что только последний вызов setTimeout примет эффект (и, следовательно, только одно из значений увеличивается).

Мне кажется, я понимаю, почему это так (значение counter постоянно в вызове обработчика события реагировать onClick), но я не знаю, как лучше структурировать этот код, чтобы избежать этой проблемы. Нужно ли буферизовать приращения и «фиксировать» изменения сразу одним вызовом setCounter? Есть ли лучший способ?

Ответы [ 2 ]

2 голосов
/ 27 апреля 2020

Чтобы добавить к ответ JMadeleine , вы можете обойти эту проблему, поместив некоторую область вокруг вызова setTimeout в виде функции.

[0,1,2].map((i) => {
  (function() {
    setTimeout(() => increment(i),1000);
  })() // <- call that new function
});

См. Эти сообщения для некоторой полезной информации

, почему javascript setTimeout () не работает в al oop?

https://coderwall.com/p/_ppzrw/be-careful-with-settimeout-in-loops

1 голос
/ 27 апреля 2020

Помещение console.log в функцию increment показывает ошибку:

  const increment = (i: number) => {
    let newCount = [...counter];
    // log values here
    console.log(i, counter)
    newCount[i] += 1;  
    setCounter(newCount);
  }

Журнал читает:

"0, [0, 0, 0]"
"1, [0, 0, 0]"
"2, [0, 0, 0]"

Значение counter всегда [0, 0, 0]. Это вызвано замыканиями .

Когда вы вызываете setTimeout(() => increment(i), 1000) для каждого из значений в [0, 1, 2], JavaScript создает в памяти три функции:

() => increment(0)

() => increment(1)

() => increment(2)

Эти функции будут выполняться через 1 секунду.

Однако, поскольку для них необходимо использовать функцию increment, функция increment также «копируется» в память.

* Функция 1026 * также использует переменную counter, поэтому переменная counter также копируется в память - ее текущее значение [0, 0, 0].

Это значение, которое будет используется во всех трех функциях.

Вот почему вы видите только последнее приращение значения, потому что вы, по сути, делаете это:

 setCounter([1, 0, 0])
 setCounter([0, 1, 0])
 setCounter([0, 0, 1])

Одним из решений было бы не используйте setTimeout вообще, , но более надежным решением является предоставление функции обратного вызова для setCounter:

const increment = (i: number) => {
  setCounter(prev => {
    let newCount = [...prev]
    newCount[i] += 1
    return newCount
  })
}

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

Вы не можете полагаться на counter как актуальное значение.


См. D. Ответ Смита для некоторых полезных ссылок, связанных с замыканиями и setTimeout.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...