Странное поведение useEffect в React.js

#reactjs

#reactjs

Вопрос:

Я испытывал какое-то странное поведение в своем приложении React, где setEffect с состоянием (скажем, state1) в качестве второго аргумента изменял другую переменную (не состояние) без вызова setState1 . Итак, я создал следующий код, чтобы посмотреть, произойдет ли то же самое, и это произошло:

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

function App() {

  let [state1, setState1] = useState('');
  let count = 0;

  useEffect(() => {
    count = count   1000;
    console.log('USEEFFECT_count: '   count);
  }, [state1]);

  setInterval(() => {
    console.log('SETINTERVAL_count: '   count);
  }, 1000);

  return (
    <div className="App">
    </div>
  );
}

export default App;

  

По сути, переменная count должна получать значение 1000 только при вызове setState1 (чего никогда не происходит) для изменения значения state1 . Но возврат консоли

 SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
SETINTERVAL_count: 1000
SETINTERVAL_count: 0
 
  

И консоль.журнал внутри useEffect никогда не вызывается, как и ожидалось. Но если это так, то:

  • почему он суммирует 1000 для подсчета?
  • почему это значение сбрасывается на 0?

Если я добавлю state2 и другой useEffect, который суммирует 5000 для подсчета, он добавляет к 1000 от первого useEffect :

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

function App() {

  let [state1, setState1] = useState('');
  let [state2, setState2] = useState('');
  let count = 0;

  useEffect(() => {
    count = count   1000;
    console.log('USEEFFECT1_count: '   count);
  }, [state1]);

  useEffect(() => {
    count = count   5000;
    console.log('USEEFFECT2_count: '   count);
  }, [state2]);

  setInterval(() => {
    console.log('SETINTERVAL_count: '   count);
  }, 1000);

  return (
    <div className="App">
    </div>
  );
}

export default App;

  

Результат console.log:

 SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0
SETINTERVAL_count: 6000
SETINTERVAL_count: 0
  

Ответ №1:

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

В вашем коде useEffect hook будет выполняться только один раз, т. Е. После первоначального рендеринга. Тогда он будет выполняться только в том случае, если state1 изменен, но этого никогда не происходит в вашем коде, поэтому useEffect выполняется только один раз и устанавливается count в 1000 значение .

И консоль.журнал внутри useEffect никогда не вызывается, как и ожидалось

Нет, он будет вызван. useEffect будет выполняться после первоначального рендеринга, поэтому count значение s становится 1000 . Наличие state1 в массиве зависимостей useEffect hook не означает, что он будет выполняться только при state1 изменении. useEffect всегда выполняется после первоначального рендеринга, независимо от того, передаете ли вы массив зависимостей или нет, или если вы передаете непустой массив зависимостей.

Теперь вопрос о том, почему setInterval регистрируются два значения count ?

Это потому, что в вашем коде установлены два интервала. Один раз во время начального рендеринга, когда count есть 0 , и второй из Strict Mode -за того, что он дважды отображает ваш компонент.

Как setInterval() вызывается на верхнем уровне из вашего компонента, новый интервал будет устанавливаться всякий раз, когда компонент повторно отображает. Поскольку ваш компонент отображается два раза, устанавливаются два интервала. Функция обратного вызова каждого интервала имеет замыкание по count сравнению с функцией обратного вызова, когда она определена.

В index.js файле вы увидите App компонент, завернутый, React.StrictMode как показано ниже:

 <React.StrictMode>
  <App/>
</React.StrictMode>
  

Если вы удалите React.StrictMode компонент, вы увидите, что count на консоль будет записано только одно значение

Вывод без строгого режима:

 USEEFFECT_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 
SETINTERVAL_count: 1000 
  

Ответ №2:

Ваш вызов setInterval находится в теле компонента, поэтому это будет происходить каждый раз, когда компонент выполняет рендеринг. Ваш компонент выполняет рендеринг дважды, поэтому он устанавливает два интервала. Каждый интервал ссылается на другую count переменную. Один из count s равен 1000, потому useEffect что соответствующий этому рендеринг выполнен. Другое значение равно 0, потому что оно useEffect не запущено (поскольку состояние 1 не изменилось).

Причина, по которой есть два рендеринга, вероятно, в том, что вы используете строгий режим react, который намеренно выполняет двойные рендеринги. Это сделано, чтобы помочь выявить ошибки, из-за которых ваш код зависит от количества рендеринга компонента. Похоже, он выполняет свою работу, поскольку подчеркивает вашу зависимость от количества рендеров.