setInterval и хуки столкнулись и перевернулись~

внешний интерфейс React.js
setInterval и хуки столкнулись и перевернулись~

Это первый день моего участия в Gengwen Challenge, смотрите подробности мероприятия:Обновить вызов

предисловие

Дело вот в чем, работать сверхурочно по выходным, чтобы наверстать упущенное по проектам, есть функция синхронных данных как асинхронный процесс, и нужно написать опрос, чтобы получить синхронные результаты. Эта функция проста, я знаком с опросом!

功能代码是使用 react hooks 写的,setInterval 并没有如我所愿的实现轮询的功能,然后我怀疑人生了? ? ?

анализ проблемы

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

Но в прошлом такого не могло быть, я должен думать дальше, зачем использовать setInterval для Waterloo и цепляться за него?

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);  
  });
  
  return <div>{count}</div>;
}

实际上上面的代码是有问题的,React 默认会在每次渲染时,都重新执行 useEffect。 ЗвонокclearIntervalПосле повторногоsetInterval

Решать проблему

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

то если мы перейдем в пустой массив[]Как зависимость, чтобы подкомпонент выполнялся при его монтировании и очищался при уничтожении компонента, может ли это решить проблему?

function Counter() {
  let [count, setCount] = useState(0);

  useEffect(() => {
    let id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <div>{count}</div>;
}

Но на самом деле после обновления таймера до 1 он останавливается. Таймер по-прежнему дает сбой, и функция опроса не может быть реализована.

Почему явление не такое, как ожидалось? На самом деле, если вы присмотритесь, то обнаружите, что это закрытая яма!

Счетчик, используемый useEffect, получается при первом рендеринге.При приобретении это0. Поскольку эффект повторно не выполнялся, поэтомуsetIntervalиспользуется в закрытияхcountвсегда с первого рендера, так что естьcount + 1всегда1Феномен. Тебя вдруг осенило! Если вы хотите получить запомненное количество хуков, вы не забудете использовать useRef в это время, и оно должно появиться на сцене~

useRef, хуки с памятью

Через две вышеупомянутые неудачи мы суммируем два противоречия, которые мы обнаружили:

1. useEffect не имеет памяти, каждый раз, когда он выполняется, он очищает предыдущий эффект и устанавливает новый эффект. Новый эффект получает новые реквизиты и состояние;

2, setInterval не забуду, это были бы ссылки на старые реквизиты и состояние, если бы оно не менялось. Но если его заменить, он сбросит время;

  • установить таймерsetInterval(fn, delay)fnпередачаsavedCallback.
  • Первый рендер, наборsavedCallbackдляcallback1
  • Второй рендер, наборsavedCallbackдляcallback2
  • ......

Давайте попробуем переписать его с помощью useRef :

function Counter() {
  let [count, setCount] = useState(0);
  const savedCallback = useRef();
  
  function callback() {
  	// 可以读取到最新的 state 和 props
  	setCount(count + 1);
	}
  
  // 每次渲染,更新ref为最新的回调
  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    let id = setInterval(() => {
       savedCallback.current();
    }, 1000);
  	return () => clearInterval(id);
  }, []);

  return <div>{count}</div>;
}

С одной стороны, его познакомили с[]Наш эффект не будет выполнен, поэтому таймер не будет сброшен. С другой стороны, поскольку настройкаsavedCallbackRef, мы можем добраться до параметров обратного вызова из последнего рендеринга, а затем вызовите, когда запускается таймер. Он имеет более низкую память данных, проблема была решена, но это слишком много проблем, читаемость бедна!

useInterval

function Counter() {
  const [count, setCount] = useState(0);

  useInterval(() => {
    setCount(count + 1);
  }, 1000);

  return <div>{count}</div>;
}

Поэтому мы настроили хук для извлечения логики и для лучшей семантики назвали его useInterval.

function useInterval(callback) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []);
}

Значение задержки здесь жестко запрограммировано, нам нужно его параметризовать, учитывая, что еслиdelayИзменено, мы также должны перезапустить таймер, поэтому нам нужно поставить задержку в зависимости от использования. Изменить это:

function useInterval(callback,delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

Ну, теперь нам не нужно обращать внимание на эту кучу бессвязной логики, чтобы использовать таймеры в хуках, нам достаточно использовать useInterval вместо setInterval.

Но что, если вы хотите приостановить таймер? Это очень просто, нам нужно только изменить логику задержки. Когда задержка равна нулю, нам не нужно устанавливать таймер. Давайте изменим его снова:

// 最终版
function useInterval(callback,delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  });

  useEffect(() => {
    function tick() {
      savedCallback.current();
    }

   	if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
  	}
  }, [delay]);
}

function Counter() {
  const [count, setCount] = useState(0);
  const [delay, setDelay] = useState(1000);
  const [isRunning, setIsRunning] = useState(true);

  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

  return <div>{count}</div>;
}

Теперь наш useInterval может обрабатывать все возможные изменения: изменение значения задержки, пауза и возобновление, что намного мощнее исходного setInterval!

Суммировать

Хуки и класс — это два разных режима программирования, мы можем столкнуться с некоторыми странными проблемами при использовании хуков, но не паникуйте, нам нужно найти основную причину проблемы, а затем изменить свое мышление, чтобы решить ее, вместо использования старое Мышление.

Наконец, спасибо, что дочитали до этого места, я иду менять код опроса, увидимся!