Как зарегистрировать событие с помощью ловушек useEffect? - PullRequest
16 голосов
/ 08 апреля 2019

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

  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.И когда я проверяю результат, странно, что при каждом вводе ключа вместо добавления он перезаписывается.

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

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

Ответы [ 6 ]

11 голосов
/ 08 апреля 2019

Лучший способ реализовать такие сценарии - это посмотреть, что вы делаете в обработчике событий.Если вы просто устанавливаете состояние с использованием предыдущего состояния, лучше всего использовать шаблон обратного вызова и регистрировать прослушиватели событий только при первоначальном монтировании.Если вы не используете callback pattern (https://reactjs.org/docs/hooks-reference.html#usecallback), ссылка на слушателей вместе с ее лексической областью используется слушателем событий, но создается новая функция с обновленным закрытием при новом рендеринге и, следовательно, в обработчике вы будетене сможет обновиться до состояния

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>
  );
1 голос
/ 08 апреля 2019

Для вашего случая использования 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

1 голос
/ 08 апреля 2019

новый ответ:

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 всегда будет привязан к начальному значению '' с аргументом [].

, вам не нужно так поступать, просто хотитесообщите, почему ваше второе решение не работает.

0 голосов
/ 08 апреля 2019

у вас нет доступа к измененному состоянию useText.Вы можете сравнить это с прежним состоянием.сохранить состояние в переменной, например: состояние, например:

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>
  );  
};
0 голосов
/ 08 апреля 2019

Вам понадобится способ отслеживать предыдущее состояние.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>
0 голосов
/ 08 апреля 2019

Во втором подходе 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, и я думаю, что первоеМетод достаточно хорош и предназначен для использования таким образом.

...