реагировать регистр одновременно setState (hooks) - PullRequest
2 голосов
/ 29 марта 2019

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

 const [activeKeys, setActiveKeys] = useState([]);

  const onKeyDown = e => {
    if (!activeKeys.includes(e.key)) {
      console.log("keydown", e.key);
      setActiveKeys([...activeKeys, e.key]);
    }
  };
  const onKeyUp = e => {
    console.log("keyup", e.key);
    setActiveKeys([...activeKeys].filter(i => i !== e.key));
  };

  useEffect(() => {
    document.addEventListener("keydown", onKeyDown);
    document.addEventListener("keyup", onKeyUp);

    return () => {
      document.removeEventListener("keydown", onKeyDown);
      document.removeEventListener("keyup", onKeyUp);
    };
  });

в основе.весь код можно найти здесь (codesandbox)

, но он работает нормально 90% времени, однако:

, когда 2 клавиши отпускаются точнов то же время состояние не обновляется дважды.

(Вы можете попытаться воспроизвести его в представленной выше кодовой ячейке, одновременно нажмите любые 2 клавиши, а затем отпустите их в одно и то же время.несколько попыток понять, вот гиф проблемы, происходящей и вот как это выглядит (число - это количество нажатых клавиш, а 's' - это клавиша, которая предположительно нажата внизтем не менее, вы можете видеть, что в журнале консоли был зарегистрирован ключ 's'.) screenshot of bug

Кто-нибудь сталкивался с чем-то подобным раньше?проблема не в

  • прослушивателе событий (console.log запускается)

  • повторного рендеринга (вы можете попробовать нажать некоторые другие клавиши,ключ останется в массиве)

Вот почему я предполагаю, что проблема заключается в крючке, однако я не могу объяснить, почему это происходит.

Ответы [ 3 ]

2 голосов
/ 29 марта 2019

Проблема в том, что вы используете простую форму setState(newValue), которая заменяет ваше состояние новым значением. Вы должны использовать функциональную форму setState( (prevState) => {} );, потому что ваше новое состояние зависит от предыдущего состояния.

Попробуйте это:

const onKeyDown = e => {
    if (!activeKeys.includes(e.key)) {
      console.log("keydown", e.key, activeKeys);
      setActiveKeys(prevActiveKeys => [...prevActiveKeys, e.key]);
    }
  };
  const onKeyUp = e => {
    console.log("keyup", e.key, activeKeys);
    setActiveKeys(prevActiveKeys =>
      [...prevActiveKeys].filter(i => i !== e.key)
    );
  };

Ссылка на песочницу

1 голос
/ 29 марта 2019

Оказывается, что мне просто нужно заменить useEffect на useLayoutEffect

из документов :

Подпись идентична useEffect,но он запускается синхронно после всех мутаций DOM.Используйте это для чтения макета из DOM и синхронного рендеринга.Обновления, запланированные внутри useLayoutEffect, будут сбрасываться синхронно до того, как браузер сможет рисовать.

0 голосов
/ 29 марта 2019

Я проверил ваши коды и обнаружил, что это больше, чем просто 2 ключа, на самом деле вы можете воспроизвести его проще с помощью большего количества ключей; попробуйте одновременно нажать asdjkl (6 клавиш), и вы увидите еще больше беспорядка.

И я думаю, что проблема в том, что цикл рендеринга реагирования не работает со слушателем событий dom, что означает, что onKeyDown и onKeyUp запускаются всякий раз, когда это необходимо, но реакция не рендерится при каждом их срабатывании. Просто войдите [...activeKeys, e.key] и [...activeKeys].filter(i => i !== e.key), и вы увидите это.

Я думаю, что решение состоит в том, чтобы использовать простую локальную переменную для обновления activeKeys, затем метод render должен использовать эту переменную вместо результата ловушки и, наконец, делать forceUpdate в каждом onKeyDown и onKeyUp чтобы отреагировать повторно.

...