Как зарегистрировать событие с помощью хуков useEffect?

Я следую курсу Udemy о том, как регистрировать события с помощью хуков, инструктор дал следующий код:

  const [userText, setUserText] = useState('');

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  });

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );

Теперь это прекрасно работает, но я не уверен, что это правильный путь. Причина в том, если я правильно понимаю, при каждом повторном рендеринге события будут продолжать регистрироваться и отменять регистрацию каждый раз, и я просто не думаю, что это правильный способ сделать это.

Поэтому я сделал небольшую модификацию крючков useEffect ниже.

useEffect(() => {
  window.addEventListener('keydown', handleUserKeyPress);

  return () => {
    window.removeEventListener('keydown', handleUserKeyPress);
  };
}, []);

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

Я ожидал, что установитьТекстПользователя(${userText}${key}); будет иметь новый типизированный ключ, добавленный к текущему состоянию и установленный как новое состояние, но вместо этого он забывает старое состояние и перезаписывает с новым состоянием.

Действительно ли это был правильный способ регистрации и отмены регистрации события при каждом повторном рендеринге?

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
113
0
116 994
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Во втором подходе useEffect привязывается только один раз и, следовательно, userText никогда не обновляется. Один из подходов — поддерживать локальную переменную, которая обновляется вместе с объектом userText при каждом нажатии клавиши.

  const [userText, setUserText] = useState('');
  let local_text = userText
  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      local_text = `${userText}${key}`;
      setUserText(local_text);
    }
  };

  useEffect(() => {
    window.addEventListener('keydown', handleUserKeyPress);

    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );

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

Не могли бы вы включить некоторый код, чтобы продемонстрировать, как достичь моей цели во втором методе?

Isaac 08.04.2019 04:51

новый ответ:

useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [])

при использовании setUserText передайте функцию в качестве аргумента вместо объекта, prevUserText всегда будет самым новым состоянием.


старый ответ:

попробуйте это, он работает так же, как ваш исходный код:

useEffect(() => {
  function handlekeydownEvent(event) {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  }

  document.addEventListener('keyup', handlekeydownEvent)
  return () => {
    document.removeEventListener('keyup', handlekeydownEvent)
  }
}, [userText])

потому что в вашем методе useEffect() это зависит от переменной userText, но вы не помещаете ее во второй аргумент, иначе userText всегда будет привязан к начальному значению '' с аргументом [].

вам не нужно делать это, просто хочу, чтобы вы знали, почему ваше второе решение не работает.

Добавление [userText] — это то же самое, что и без второго аргумента, верно? Причина в том, что у меня есть только userText в приведенном выше примере, и без второго аргумента просто означает повторную визуализацию при каждом изменении реквизита/состояния, я не понимаю, как это отвечает на мой вопрос. **P/S: ** Я не против, в любом случае спасибо за ответ

Isaac 08.04.2019 04:53

эй @Isaac, да, это то же самое, что и без второго аргумента, я просто хочу, чтобы вы знали, почему ваше второе решение не работает, потому что ваше второе решение useEffect() зависит от переменной userText, но вы не указали вторые аргументы .

Spark.Bao 08.04.2019 05:08

Но добавление [userText] также означает регистрацию и отмену регистрации события при каждом повторном рендеринге, верно?

Isaac 08.04.2019 05:14

точно! вот почему я говорю, что это то же самое с вашим первым решением.

Spark.Bao 08.04.2019 05:17

ха-ха, да, я понял идею, но дело в том, что я чувствую, что это неэффективно, потому что я чувствую, что событие не должно быть register и deregister при каждом повторном рендеринге, а вместо этого должно быть как классические componentDidMount и componentWillUnmount, где они только зарегистрируйтесь один раз, если вы поняли мою точку зрения?

Isaac 08.04.2019 05:19

Хотя ваша текущая рекомендация — зарегистрироваться на мероприятии componentDidUpdate, что не так, как мы все сделали правильно?

Isaac 08.04.2019 05:20

понял, что вы имеете в виду, если вы действительно хотите зарегистрировать его только один раз в этом примере, вам нужно использовать useRef, как ответ @Maaz Syed Adeeb.

Spark.Bao 08.04.2019 05:23

Я только что проголосовал за ваш ответ за то, что вы прошли через это со мной. Интересно, исходя из вашего понимания, является ли useRef более рекомендуемым способом по сравнению с ответом di3 выше?

Isaac 08.04.2019 05:47

@ Исаак, в моем понимании, да.

Spark.Bao 08.04.2019 07:42

эй, @Исаак, теперь у меня новое понимание, тебе не нужны ни useRef, ни useCallback, только useState и useEffect, см. мой новый ответ.

Spark.Bao 11.04.2019 03:17

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

const prevRef = useRef();
useEffect(() => {
  prevRef.current = userText;
});

Я обновил ваш пример, чтобы использовать это. И это работает.

const { useState, useEffect, useRef } = React;

const App = () => {
  const [userText, setUserText] = useState("");
  const prevRef = useRef();
  useEffect(() => {
    prevRef.current = userText;
  });

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${prevRef.current}${key}`);
    }
  };

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id = "root"></div>

Для вашего варианта использования useEffect нужен массив зависимостей для отслеживания изменений, и на основе зависимости он может определить, следует ли повторно отображать или нет. Всегда рекомендуется передавать массив зависимостей в useEffect. Пожалуйста, посмотрите код ниже:

Я представил useCallback крючок.

const { useCallback, useState, useEffect } = React;

  const [userText, setUserText] = useState("");

  const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(prevUserText => `${prevUserText}${key}`);
    }
  }, []);

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [handleUserKeyPress]);

  return (
    <div>
      <blockquote>{userText}</blockquote>
    </div>
  );

Edit q98jov5kvq

Я пробовал ваше решение, но оно точно такое же, как [userText] или без второго аргумента. По сути, мы помещаем console.info внутри useEffect, и мы увидим, что журналирование запускает каждый повторный рендеринг, что также означает, что addEventListender запускается при каждом повторном рендеринге.

Isaac 08.04.2019 05:26

Я хочу верить, что это ожидаемое поведение. Я обновил свой ответ.

codejockie 08.04.2019 05:39

В песочнице вы поместили оператор console.info('>'); в хуки useEffect, и, используя ваш обновленный код, он по-прежнему регистрируется каждый раз, что также означает, что события по-прежнему регистрируются при каждом повторном рендеринге.

Isaac 08.04.2019 05:45

Нет, для addEventListener это так не работает. Он не вызывается несколько раз. developer.mozilla.org/en-US/docs/Web/API/EventTarget/…

codejockie 08.04.2019 05:48

но из-за return () => {window.removeEventListener('keydown', handleUserKeyPress)} при каждом повторном рендеринге компонент будет регистрироваться и отменять регистрацию

Isaac 08.04.2019 06:00

Мы можем поместить внутрь еще один консольный журнал return () => { console.info('im removing!')} и увидеть, что при каждом нажатии клавиши событие будет отменяться и перерегистрироваться.

Isaac 08.04.2019 06:01

Именно то поведение, которое я хотел, но вы можете наблюдать его @ codeandbox.io/s/n5j7qy051j

Isaac 08.04.2019 06:06

@Isaac, он не будет перерегистрироваться при каждом оказывать, только при каждом изменении userText (то есть при большинстве, но не при всех нажатиях клавиш)

Aprillion 08.04.2019 13:53

@Aprillion: я понимаю вашу точку зрения, но не думаю, что это решение — то, что я действительно ищу. Вместо этого вы можете взглянуть на принятый ответ

Isaac 08.04.2019 14:18

у вас нет доступа к измененному состоянию us eText. вы можете сравнить его с prevState. сохранить состояние в переменной, например: состояние следующим образом:

const App = () => {
  const [userText, setUserText] = useState('');

  useEffect(() => {
    let state = ''

    const handleUserKeyPress = event => {
      const { key, keyCode } = event;
      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        state += `${key}`
        setUserText(state);
      }   
    };  
    window.addEventListener('keydown', handleUserKeyPress);
    return () => {
      window.removeEventListener('keydown', handleUserKeyPress);
    };  
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );  
};
Ответ принят как подходящий

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

Если вы просто устанавливаете state, используя предыдущий state, лучше всего использовать шаблон обратного вызова и регистрировать прослушиватели событий только на монтировании исходный.

Если вы не используете шаблон обратного вызова, ссылка прослушивателя вместе с его лексической областью действия используется прослушивателем событий, но создается новая функция с обновленным закрытием при каждом рендеринге; следовательно, в обработчике вы не сможете получить доступ к обновленному состоянию

const [userText, setUserText] = useState("");
const handleUserKeyPress = useCallback(event => {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)){
        setUserText(prevUserText => `${prevUserText}${key}`);
    }
}, []);

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

  return (
      <div>
          <h1>Feel free to type!</h1>
          <blockquote>{userText}</blockquote>
      </div>
  );

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

di3 08.04.2019 10:51

@di3 Если вы используете useCallback для определения функции с пустым массивом зависимостей, у вас также не будет этой проблемы.

Shubham Khatri 08.04.2019 12:00

если handleUserKeyPress не втягивается внутрь useEffect, то это внешняя зависимость и должна быть явной: useEffect(..., [handleUserKeyPress])

Aprillion 08.04.2019 13:51

@Aprillion, это неправда, пустой массив специально используется для проверки монтирования/размонтирования. намерение состоит в том, чтобы вызвать его только один раз.

di3 08.04.2019 16:04

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

Aprillion 08.04.2019 16:27

Я вижу, что важно использовать prevUserText для ссылки на предыдущее состояние userText. что, если мне нужно получить доступ к нескольким состояниям? Как я могу получить доступ ко всему предыдущему состоянию?

Joey Yi Zhao 04.06.2019 13:48

Как мы можем получить предыдущее состояние, если мы используем useReducer вместо useState?

Eesa 08.10.2019 11:01

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

Jeremy Nelson 05.12.2019 03:26

@JeremyNelson, если под доступом к текущему состоянию вы подразумеваете доступ к обновленному состоянию, вы можете сделать это либо с помощью нового useEffect, либо, поскольку у вас уже есть значение, которое нужно вернуть из setUserText, вы можете использовать его перед возвратом из его обратного вызова.

Shubham Khatri 05.12.2019 06:41

@ShubhamKhatri, это именно то, что я искал! <3 Может ли кто-нибудь, разбирающийся в хуках, объяснить в нескольких словах, когда передавать параметр самому эффекту (первый аргумент useEffect), а когда нет? Кажется, что это работает нормально в обоих случаях, когда я регистрирую обновление состояния в манере didupdate, но мне не ясно, подходит ли это для более сложных случаев.

rtviii 09.01.2020 03:51

@tractatusviii Думаю, этот пост вам поможет: stackoverflow.com/questions/55840294/…

Shubham Khatri 09.01.2020 04:27

@Eesa У вас всегда есть предыдущее состояние, доступное вам в функции редуктора, предоставленной useReducer. Функция обратного вызова prevState — это обходной путь для useState.

izolate 19.06.2020 19:56

У меня была такая же проблема, и я долго с ней боролся. но ваше решение спасло мой день :)

Morteza Rahmani 25.01.2022 13:15

Проблема

[...] on each and every re-render, events will keep registering and deregistering every time and I simply don't think it is the right way to go about it.

Ты прав. Нет смысла перезапускать обработку событий внутри useEffect на рендере каждый.

[...] empty array as the second argument, letting the component to only run the effect once [...] it's weird that on every key I type, instead of appending, it's overwritten instead.

Это проблема с устаревшие значения закрытия.

Причина: Используемые функции внутри useEffect должны быть часть зависимостей. Вы ничего не устанавливаете в качестве зависимости ([]), но все равно вызываете handleUserKeyPress, который сам считывает состояние userText.

Решения

Есть несколько альтернатив в зависимости от вашего варианта использования.

1. Функция обновления состояния

setUserText(prev => `${prev}${key}`);

✔ наименее инвазивный подход
✖ доступ только к собственному предыдущему состоянию, а не к другим состояниям

const App = () => {
  const [userText, setUserText] = useState("");

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(prev => `${prev}${key}`); // use updater function here
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []); // still no dependencies

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

2. useReducer - "мошеннический режим"

Мы можем переключиться на useReducer и получить доступ к текущему состоянию/реквизиту — с API, аналогичным useState.

Вариант 2а: логика внутри функции редуктора

const [userText, handleUserKeyPress] = useReducer((state, event) => {
    const { key, keyCode } = event;
    // isUpperCase is always the most recent state (no stale closure value)
    return `${state}${isUpperCase ? key.toUpperCase() : key}`;  
}, "");

const App = () => {
  const [isUpperCase, setUpperCase] = useState(false);
  const [userText, handleUserKeyPress] = useReducer((state, event) => {
    const { key, keyCode } = event;
    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      // isUpperCase is always the most recent state (no stale closure)
      return `${state}${isUpperCase ? key.toUpperCase() : key}`;
    }
  }, "");

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
      <button style = {{ width: "150px" }} onClick = {() => setUpperCase(b => !b)}>
        {isUpperCase ? "Disable" : "Enable"} Upper Case
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

Вариант 2б: логика вне функции редуктора - аналогично useState функции обновления

const [userText, setUserText] = useReducer((state, action) =>
      typeof action === "function" ? action(state, isUpperCase) : action, "");
// ...
setUserText((prevState, isUpper) => `${prevState}${isUpper ? key.toUpperCase() : key}`);

const App = () => {
  const [isUpperCase, setUpperCase] = useState(false);
  const [userText, setUserText] = useReducer(
    (state, action) =>
      typeof action === "function" ? action(state, isUpperCase) : action,
    ""
  );

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;
      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(
          (prevState, isUpper) =>
            `${prevState}${isUpper ? key.toUpperCase() : key}`
        );
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
      <button style = {{ width: "150px" }} onClick = {() => setUpperCase(b => !b)}>
        {isUpperCase ? "Disable" : "Enable"} Upper Case
      </button>
    </div>
  );
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

✔ нет необходимости управлять зависимостями
✔ доступ к нескольким состояниям и реквизиту
✔ тот же API, что и у useState
✔ расширяется до более сложных случаев/редукторов
✖ немного меньшая производительность из-за встроенного редуктора (своего рода пренебрежительный)
✖ немного увеличена сложность редуктора

3. useRef / сохранить обратный вызов в изменяемой ссылке

const cbRef = useRef(handleUserKeyPress);
useEffect(() => { cbRef.current = handleUserKeyPress; }); // update after each render
useEffect(() => {
    const cb = e => cbRef.current(e); // then use most recent cb value
    window.addEventListener("keydown", cb);
    return () => { window.removeEventListener("keydown", cb) };
}, []);

const App = () => {
  const [userText, setUserText] = useState("");

  const handleUserKeyPress = event => {
    const { key, keyCode } = event;

    if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
      setUserText(`${userText}${key}`);
    }
  };

  const cbRef = useRef(handleUserKeyPress);

  useEffect(() => {
    cbRef.current = handleUserKeyPress;
  });

  useEffect(() => {
    const cb = e => cbRef.current(e);
    window.addEventListener("keydown", cb);

    return () => {
      window.removeEventListener("keydown", cb);
    };
  }, []);

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>

✔ для обратных вызовов/обработчиков событий, не используемых для повторного рендеринга потока данных
✔ нет необходимости управлять зависимостями
✖ рекомендуется только как последний вариант в документации React
✖ более императивный подход

Взгляните на эти ссылки для получения дополнительной информации: 123

Неподходящие решения

useCallback

Хотя его можно применять по-разному, useCallback не подходит для этого конкретного случая вопроса.

Причина: из-за добавленных зависимостей — userText здесь — прослушиватель событий будет перезапущен при нажатии клавиши каждый, в лучшем случае не будет работать или, что еще хуже, вызовет несоответствия.

const App = () => {
  const [userText, setUserText] = useState("");

  const handleUserKeyPress = useCallback(
    event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(`${userText}${key}`);
      }
    },
    [userText]
  );

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [handleUserKeyPress]); // we rely directly on handler, indirectly on userText

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef, useCallback } = React</script>

Для полноты картины, вот несколько ключевых моментов по useCallback в целом:

✔ универсальное практичное решение
✔ минимально инвазивный
✖ ручное управление зависимостями
useCallback делает определение функции более подробным/загроможденным

Объявить функцию-обработчик внутри useEffect

Объявление события функция-обработчик прямо внутри useEffect имеет более или менее те же проблемы, что и useCallback, последнее просто вызывает немного большую косвенность зависимостей.

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

На самом деле, если вы переместите handleUserKeyPress внутрь useEffect, исчерпывающее правило отложений ESLint сообщит вам, какие именно канонические зависимости отсутствуют (userText), если не указано иное.

const App =() => {
  const [userText, setUserText] = useState("");

  useEffect(() => {
    const handleUserKeyPress = event => {
      const { key, keyCode } = event;

      if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
        setUserText(`${userText}${key}`);
      }
    };

    window.addEventListener("keydown", handleUserKeyPress);

    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  }, [userText]); // ESLint will yell here, if `userText` is missing

  return (
    <div>
      <h1>Feel free to type!</h1>
      <blockquote>{userText}</blockquote>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src = "https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

Это начинает все больше и больше походить на пропущенный недостаток дизайна хуков.

foxtrotuniform6969 07.11.2021 00:22

Принятый ответ работает, но если вы регистрируете событие BackHandler, убедитесь, что return true в вашей функции handleBackPress, например:

const handleBackPress= useCallback(() => {
   // do some action and return true or if you do not
   // want the user to go back, return false instead
   return true;
 }, []);

 useEffect(() => {
    BackHandler.addEventListener('hardwareBackPress', handleBackPress);
    return () =>
       BackHandler.removeEventListener('hardwareBackPress', handleBackPress);
  }, [handleBackPress]);

Другие вопросы по теме