понимание странного поведения setState в reactjs с использованием хуков useState

const[count,setcount]=useState(0)
function inc() {
setcount(count + 5);
setcount(count + 5);
setcount(count + 5);
setcount(count + 5);
}
return (
<div>
  <h1>{count}</h1> #output =5
  <button onClick={() => inc()}>clickme</button>
</div>
);




}
function inc() {
setcount(count=>count + 5);
setcount(count=>count + 5);
setcount(count=>count + 5);
setcount(count=>count + 5);
}
return (
<div>
  <h1>{count}</h1> #output =5
  <button onClick={() => inc()}>clickme</button>
</div>
);
}

Когда я вызываю первую функцию при нажатии кнопки, значение счетчика становится равным 5, а во второй функции - 20. Я не могу понять, почему такое поведение


person Fazal Ur Rehman Fazal    schedule 18.11.2020    source источник


Ответы (2)


Тот факт, что эти переменные возвращаются из хука React, не заставляет их вести себя иначе, чем любую другую переменную JS. const является константой; это не может измениться. Рассмотрим следующий пример без React.

function logValue(input) {
  console.log(input);
}

const value = 0;

logValue(value + 5);
logValue(value + 5);
logValue(value + 5);

Он регистрирует 5 каждый раз. Это то, что вы ожидаете, верно? Поскольку value является константой и всегда будет равно 0. Вы можете подумать, что это не то же самое, потому что вы не ведете журнал, а обновляете. Но это действительно не отличается. Функции обновления состояния не просто обновляют переменную типа count = 5 (подсказка: это сломается, см. ниже).

const value = 0;

function updateValue(input) {
  value = input;
}

updateValue(value + 5);

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

Таким образом, даже если вы вызываете функцию обновления более одного раза, значение, которое вы отправляете для обновления, равно всегда 5.

Что касается функционального обновления (setCount(count => count + 5)), здесь count относится не к константе состояния, а к параметру count (название сбивает с толку, поскольку переменная состояния затеняется параметром). Этот параметр гарантированно содержит самое последнее значение состояния (для помощь с асинхронными ограничениями). Вот почему второй набор работает так, как ожидалось.

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

const {useState, useEffect} = React;

const Example = () => {
  const [count, setCount] = useState(0);
  
  const regularUpdate = () => {
    console.log('count before update:', count);
    setCount(count + 5);
    console.log('count after 1 update:', count);
    setCount(count + 5);
    console.log('count after 2 updates:', count);
    setCount(count + 5);
    console.log('count after 3 updates:', count);
  }
  
  const functionalUpdate = () => {
    console.log('count before update:', count);
    setCount(count => {
      console.log('count inside 1st update:', count);
      return count + 5
    });
    setCount(count => {
      console.log('count inside 2nd update:', count);
      return count + 5
    });
    setCount(count => {
      console.log('count inside 3rd update:', count);
      return count + 5
    });
    console.log('count after updates:', count);
  }
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={regularUpdate}>Run normal updates</button>
      <button onClick={functionalUpdate}>Run functional updates</button>
      <button onClick={() => {setCount(0); console.clear();}}>Reset</button>
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));
<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="root"></div>

Стоит обратить внимание на порядок журналов при запуске функциональных обновлений. Обратите внимание, что счетчик после обновлений регистрируется перед любым из журналов, внутренних для функциональных обновлений, и что он по-прежнему содержит необновленное значение.

person Brian Thompson    schedule 18.11.2020

setcount — это асинхронный метод, поэтому 4 setcount будут выполняться одновременно с начальным значением count (0) для первого метода inc(). Таким образом, результатом будет 5 даже 4 выполненных setcounts.

Для второго метода inc() используется функция, которая получит предыдущее значение и вернет обновленное значение, поэтому результат равен 20, как и ожидалось.

person wangdev87    schedule 18.11.2020
comment
если у меня есть последний оператор setCount (count + 10), он переопределяет значение состояния, почему? - person Fazal Ur Rehman Fazal; 18.11.2020
comment
результат будет 10 - person wangdev87; 18.11.2020
comment
почему это так? - person Fazal Ur Rehman Fazal; 18.11.2020
comment
setCount(count+5); setCount(count+10); когда эти 2 команды будут готовы к выполнению, параметр count будет иметь то же значение. потому что setCount — это асинхронный метод. - person wangdev87; 18.11.2020
comment
тогда почему последнее значение setCount() имеет предпочтение перед предыдущим setCount()? - person Fazal Ur Rehman Fazal; 18.11.2020
comment
команды будут выполняться синхронно, а setCount(count+5) будет выполняться немного раньше, чем setCount(count+10), поэтому setCount(count+10) будет завершен немного позже, чем setCount(count+5) - person wangdev87; 18.11.2020