Почему мой обработчик onClick не выводит последнюю версию state и как я могу устранить это?

#javascript #reactjs

#javascript #reactjs

Вопрос:

Песочница

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

export default function App() {
  let [button, setButton] = useState(null);
  let [num, setNum] = useState(5);

  function revealState() {
    console.log(num);
  }
  function changeState() {
    setNum(Math.random());
  }

  useEffect(() => {
    const el = (
      <button id="logStateButton" onClick={revealState}>
        Log state
      </button>
    );
    setButton(el);
  }, []);

  return (
    <>
      {button}
      <button onClick={changeState}>Change state</button>
    </>
  );
}
 

Нажатие на кнопку «Log state» успешно регистрирует num состояние. Нажатие на кнопку «Изменить состояние» успешно изменяет num состояние. Повторное нажатие кнопки «Войти в состояние» не регистрирует обновленное значение состояния — оно регистрирует старое.

  1. Почему это так? Я предполагаю, что, поскольку useEffect выполняется только один раз, он ссылается только на первую revealState функцию, которая ссылается только на первую num переменную. Поскольку этого нет в return инструкции компонента, он не обновляется.
  2. Какова бы ни была причина проблемы, какие есть обходные пути? Некоторые из требований:
  • тег не может быть отображен непосредственно в return инструкции.
  • у нас должно быть useEffect то, что есть, и у него должен быть какой-то массив dep (нежелательно, чтобы он запускался каждый раз, когда функциональный компонент выполняется повторно).
  • В реальном проекте могут быть внесены некоторые важные изменения в useEffect рендеринг обратного вызова тегов — поэтому нецелесообразно повторно запускать useEffect , помещая что-то подобное num в его массив dep.

Комментарии:

1. Почему вы монтируете элементы из крючка useEffect?

2. удалите этот пустой массив в конце useEffect() и сообщите мне результат

3. @theTradeCoder Второе предложение в пункте 2 дает вам понять, что я хотел бы сохранить это

4. Используя этот массив в конце, он заставляет useEffect() запускаться только один раз при каждой загрузке / перезагрузке

5. проверьте ответ ниже, помогает ли это вашей потребности

Ответ №1:

IMO, самое правильное решение — просто добавлять обновленный прослушиватель событий при каждом отображении страницы:

 useEffect(() => {
  el.onclick = onClickHandler
});
 

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

Ответ №2:

 import React, { useEffect, useState, useCallback } from "react";

export default function App() {
  let [button, setButton] = useState(null);
  let [num, setNum] = useState(5);

  const revealState = useCallback(() => {
    console.log(num);
  }, [num])
  function changeState() {
    setNum(Math.random());
  }

  useEffect(() => {
    const el = (
      <button id="logStateButton" onClick={revealState}>
        Log state
      </button>
    );
    setButton(el);
  }, [revealState]);

  return (
    <>
      {button}
      <button onClick={changeState}>Change state</button>
    </>
  );
}
 

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

Комментарии:

1. Работает, но я не могу запустить его повторно useEffect , потому что некоторые важные изменения в тегах могут быть внесены в другом месте. Я изменил свой вопрос, сказав, что решение должно отражать это. Но все равно спасибо.

Ответ №3:

вы должны добавить num в качестве зависимости к useEffect :

   useEffect(() => {
      const el = (
          <button id="logStateButton" onClick={revealState}>
              Log state
          </button>
      );
      setButton(el);
  }, [num]);
 

Комментарии:

1. Это действительно работает, однако я не могу повторно запустить useEffect его, так как могут быть внесены некоторые важные изменения в теги. Я изменил свой вопрос, чтобы включить это сейчас. Хотя спасибо. Возможно, вы могли бы взглянуть на мою попытку решения последней ссылки на песочницу, указанной в вопросе? Интересно, является ли моя проблема стандартной проблемой и есть ли стандартное решение.

Ответ №4:

После дополнительных разъяснений по вашей проблеме, похоже, вам нужно следить как за числом, так и за состоянием HTML. Объединение кода Алана и Кишора вместе — вот решение проблемы. Поскольку useEffect отслеживает только число в ответе Алана, поэтому, если какие-либо изменения в теге не приведут к его повторному запуску. кишор также упомянул еще одно исправление, которое заключается в использовании useCallback, но то, что нужно смотреть, — это the button , а не the num . Вот так:

 const updateButton = useCallback(function (newButton) {
    setButton(newButton);
  }, [button])

useEffect(() => {
    const el = (
      <button id="logStateButton" onClick={revealState}>
        Log state
      </button>
    );
    updateButton(el)
  }, [num]);
 

Это укажет useEffect на просмотр num и вернет новое button только при button изменении состояния.

Комментарии:

1. Это было бы слишком подробно, если бы у нас было много обработчиков onClick с разными обратными вызовами. Действительно ли я хочу каждый раз перерисовывать теги заново? Особенно, когда важные изменения в моих тегах, возможно, произошли из других источников, которые я теперь буду перезаписывать? Конечно, должно быть что-то попроще.