Состояние реакции внутри функции не меняется даже после вызова с задержкой (5 секунд) - PullRequest
0 голосов
/ 14 марта 2020

В ответ я использую функциональный компонент, и у меня есть две функции (getBooks) и (loadMore)

getBooks получение данных из конечной точки. Но когда я вызываю функцию loadMore по нажатию кнопки внутри функции getBooks (loadMoreClicked), она не изменяется, она использует предыдущее состояние даже после вызова с задержкой (5 секунд). Но когда я звоню loadMore снова состояние меняется и все работает нормально.

может кто-нибудь объяснить, почему (loadMoreClicked) при первоначальном вызове (getBooks) не обновлялся, даже вызывая его после 5-секундной задержки.

function component() {
  const [loadMoreClicked, setLoadMore] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`; //this is my end point
    axios
      .get(endPoint, {
        params: newFilters
      })
      .then(res => {
        console.log(loadMoreClicked); //the (loadMoreClicked) value is still (false) after (5 sec)
      })
      .catch(err => {
        console.log(err);
      });
  };

  const loadMore = () => {
    setLoadMore(true); //here i am changing (loadMoreClicked) value to (true)

    setTimeout(() => {
      getBooks(); // i am calling (getBooks()) after 5 seconds.
    }, 5000);
  };

  return (
    <div>
      <button onClick={() => loadMore()}>loadMore</button> //calling (loadMore)
      function
    </div>
  );
}

Ответы [ 2 ]

2 голосов
/ 14 марта 2020

Происходит две вещи:

  1. getBooks() использует const значения, которые определены в окружающей функции. Когда функция ссылается на переменные const или let вне ее определения, она создает то, что называется замыканием . Замыкания берут значения из этих внешних переменных и дают внутренним функциям копии значений как они были, когда функция была построена. В этом случае функция была построена сразу после первоначального вызова состояния, с loadMoreClicked установлен на false.

  2. Так почему же setLoadMore(true) не вызвал повторное рендеринг и не переписал функцию? Когда мы устанавливаем состояние, повторное отображение не происходит мгновенно. Он добавляется в очередь, которой управляет React. Это означает, что при выполнении loadMore(), setLoadMore(true) говорит: «обновите состояние после того, как я закончу выполнять остальную часть кода». Повторное отображение происходит после окончания функции, поэтому используемая копия getBooks() - это копия, созданная и поставленная в очередь в этом цикле, со встроенными исходными значениями.

Для чего вы Вы можете захотеть, чтобы в вашем тайм-ауте вызывались разные функции, в зависимости от того, была ли нажата кнопка. Или вы можете создать другое, более немедленное закрытие, в зависимости от того, хотите ли вы getBooks() считать кнопку нажатой или нет, например:

const getBooks = wasClicked => // Now calling getBooks(boolean) returns the following function, with wasClicked frozen
  () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(wasClicked); // This references the value copied when the inner function was created by calling getBooks()
    })
    .catch(err => {
      console.log(err);
    });
  }

...

const loadMore = () => {
  setLoadMore(true);
  setTimeout(
    getBooks(true), // Calling getBooks(true) returns the inner function, with wasClicked frozen to true for this instance of the function
    5000
  );
};

Существует третий вариант, который переписывает const [loadMoreClicked, setLoadMore] до var [loadMoreClicked, setLoadMore]. Если ссылка на переменные const замораживает значение в этот момент, var - нет. var позволяет функции динамически ссылаться на переменную, так что значение определяется при выполнении функции, а не когда она была определена.

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

Моя личная рекомендация - сохранить определения const. var используется сообществом разработчиков менее часто из-за путаницы в том, как это работает в замыканиях по сравнению со стандартными функциями. Большинство, если не все крючки, заселяют консты на практике. Использование этого в качестве единственного var ссылки приведет в замешательство будущих разработчиков, которые, вероятно, сочтут это ошибкой и изменит ее в соответствии с шаблоном, нарушая ваш код.

Если вы хотите динамически ссылаться на состояние loadMoreClicked, и вам не обязательно нужен компонент для рендеринга, я бы на самом деле рекомендовал использовать useRef() вместо useState().

useRef для создания объекта с одним свойством, current , который имеет значение, которое вы вкладываете в него. Когда вы изменяете current, вы обновляете значение изменяемого объекта. Таким образом, даже если ссылка на объект заморожена во времени, она ссылается на объект, который доступен с самым текущим значением.

Это будет выглядеть так:

function component() {
  const loadMoreClicked = useRef(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      console.log(loadMoreClicked.current); // This references the property as it is currently defined
    })
    .catch(err => {
      console.log(err);
    });
 }


  const loadMore = () => {
    loadMoreClicked.current = true; // property is uodated immediately
    setTimeout(getBooks(), 5000);
  };

}

Это работает, потому что в то время как loadMoreClicked определяется как const сверху, это постоянная ссылка на объект, не постоянное значение. Однако объект, на который ссылаются, может быть видоизменен вам нравится.

Это одна из самых запутанных вещей в Javascript, и обычно она закрывается в уроках, поэтому, если вы не вступаете с каким-то внутренним опытом с указателями, такими как C или C ++, это будет странно.

Итак, для того, что вы делаете, я бы рекомендовал использовать useRef () вместо useState (). Если вы действительно хотите повторно визуализировать компонент, скажем, если вы хотите отключить кнопку во время загрузки контента, а затем снова включить ее при загрузке контента, я, вероятно, использовал бы оба, и переименовал бы их, чтобы прояснить их назначение :

function component() {
  const isLoadPending = useRef(false);
  const [isLoadButtonDisabled, setLoadButtonDisabled] = useState(false);
  const getBooks = () => {
    const endPoint = `http://localhost/getBooks`;
    axios
    .get(endPoint, {
      params: newFilters
    })
    .then(res => {
      if (isLoadPending.current) {
        isLoadPending.current = false:
        setLoadButtonDisabled(false);
      }
    })
    .catch(err => {
      console.log(err);
    });
 };

  const loadMore = () => {
    isLoadPending.current = true;
    setLoadButtonDisabled(true);
    setTimeout(getBooks(), 5000);
  };

}

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

Установка состояния - операция «забыл и забыл». На самом деле вы не увидите изменений в нем, пока не будет выполнена вся функция вашего компонента. Имейте в виду, что вы получите свое значение, прежде чем сможете использовать функцию setter. Поэтому, когда вы устанавливаете состояние, вы ничего не меняете в этом цикле, вы говорите React запустить другой цикл. Он достаточно умен, чтобы ничего не визуализировать до завершения второго цикла, поэтому он быстрый, но он все еще выполняет два полных цикла, сверху вниз.

0 голосов
/ 14 марта 2020

вы можете использовать метод useEffect, чтобы отслеживать loadMoreClicked обновления, такие как componentDidUpdate метод жизненного цикла, и вызывать setTimeout внутри него,

useEffect(() => {
    if(loadMoreClicked){
      setTimeout(() => {
        getBooks();
      }, 5000);
    }
  }, [loadMoreClicked])

таким образом только после loadMoreClicked изменяется на true мы называем setTimeout.

Это сводится к тому, как замыкания работают в JavaScript. Функция, переданная setTimeout, получит переменную loadMoreClicked из начального рендера, поскольку loadMoreClicked не является мутированным.

...