Ловушка React не обновлена ​​в функции прослушивания события прокрутки - PullRequest
6 голосов
/ 19 июня 2019

У меня есть функция handleScroll, которая прослушивается при событии прокрутки.Эта функция должна обновить isFetching (которая запускается как false и должна изменить логическое значение).

Функция handleScroll прослушивается правильно, как показывает console.log.Однако isFetching всегда ложно.Кажется, что setIsFetching никогда не читается.Я думаю, что другой вариант, как будто EventListener замораживает первую версию функции handleScroll.

Как это сделать, чтобы обновить хук в этой функции? Вот упрощенная версиякода и codesandbox :

/* <div id='root'></div> */
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
    if (immediate && !timeout) func.apply(context, args);
  };
};
const App = () => {
  const [isFetching, setIsFetching] = useState(false);
  const handleScroll = debounce(() => {
    setIsFetching(!isFetching);
    console.log({ isFetching });
  }, 300);
  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);
  return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);

ОБНОВЛЕНИЕ

Я поместил пустой массив в качестве второго параметра в useEffect потому что я хочу, чтобы первая функция param запускалась только один раз для componentDidMount ()

Ответы [ 3 ]

1 голос
/ 19 июня 2019

Добавьте isFetching в качестве зависимости от useEffect

Хотя я не могу дать глубокое объяснение, я могу сказать, что вы в основном лгали React в useEffect, когда сказали, что эффект не 'не полагаясь ни на что, предоставляя пустой массив зависимостей, всегда хорошо передавать все переменные, включенные в ваш эффект.

Кроме того, вы создаете новую функцию каждый раз, когда компонент re-render, чтобы избежать этого перемещенияфункция внутри useEffect или оболочка внутри useCallback, которая не создаст воссоздание функции, если что-то в массиве зависимостей не изменится

useEffect(
  () => {
    const handleScroll = debounce(() => {
      setIsFetching(prevState => !prevState);
      console.log({ isFetching });
    }, 300);
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  },
  [isFetching]
);

Или с помощью useCallback

const handleScroll = useCallback(
  debounce(() => {
    setIsFetching(prevState => !prevState);
    console.log({ isFetching });
  }, 300),
  [isFetching]
);

useEffect(
  () => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  },
  [isFetching]
);

полное руководство по использованиюЭффект

1 голос
/ 19 июня 2019

Вы передали пустой массив в качестве второго аргумента для использованияEffect.И это то, что является вашей ключевой проблемой для того, чтобы не вызывать useEffect после его запуска.

Чтобы решить эту проблему, просто не передавайте второй аргумент.

 useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  });

Или вызовитеuseEffect всякий раз, когда изменяется свойство:

useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, [isFetching]); // re-run useEffect on isFetching changed

Это похоже на то, что мы делаем в componentDidUpdate:

if (prevState.count !== this.state.count) {
  // do the stuff

Для получения более подробной информации см. документацию ..

Примечание из документов:

Если вы хотите запустить эффект и очистить его только один раз (при монтировании и размонтировании), вы можетепередать пустой массив ([]) в качестве второго аргумента.Это говорит React, что ваш эффект не зависит от каких-либо значений из реквизита или состояния, поэтому его не нужно повторно запускать.Это не обрабатывается как особый случай - это следует непосредственно из того, как всегда работает массив зависимостей.

Если вы передадите пустой массив ([]), реквизиты и состояние внутри эффекта всегда будут иметь свои начальные значения.ценности.Хотя передача [] в качестве второго аргумента ближе к знакомой ментальной модели componentDidMount и componentWillUnmount, обычно существуют лучшие решения, позволяющие избежать слишком частого повторного запуска эффектов.Кроме того, не забывайте, что React откладывает запуск useEffect до тех пор, пока браузер не выполнит рисование, поэтому выполнение дополнительной работы представляет собой меньшую проблему.

1 голос
/ 19 июня 2019

Чтобы прослушивать изменения в вашем состоянии из-за обратного вызова useEffect (когда вы не следите за обновлениями реквизита), вы можете сохранить свое состояние в переменной вне области видимости вашего компонента и использовать его. вместо государства напрямую.

Здесь у вас есть код:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

const debounce = (func, wait, immediate) => {
  let timeout;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      timeout = null;
      if (!immediate) func.apply(context, args);
    }, wait);
    if (immediate && !timeout) func.apply(context, args);
  };
};

let isFetchingState;

const App = () => {
  const [isFetching, setIsFetching] = useState(false);

  isFetchingState = isFetching;

  const handleScroll = debounce(() => {
    setIsFetching(!isFetchingState);
    console.log({ isFetchingState });
  }, 300);

  useEffect(() => {
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return <div style={{ height: "1280px" }}>Hello world</div>;
};

const root = document.getElementById("root");

if (root) ReactDOM.render(<App />, root);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...