React: Ошибка: несвязанное обновление состояния в onKeyDown блокирует onChange и нарушает управляемый компонент

Созданный на 9 июл. 2020  ·  3Комментарии  ·  Источник: facebook/react

Привет, я использую режим управления вводом и подписываюсь на события onKeyDown и onChange . Это вызовет эффект, когда пользователь войдет в пространство. Однако пространство не отображается на экране. Это ошибка в React или что-то не так с тем, как я его использую? Но подписаться на onKeyUp - это нормально. Это сбивает с толку.

Версия React: 16.13.1 и старше

Действия по воспроизведению

  1. войти в космос
  2. элемент ввода не показывает пространство на экране

Ссылка на пример кода:

import React, { useState,useEffect } from 'react'

const App = () => {
  const [count, setCount] = useState(0)
  const [value, setValue] = useState('')
  const [code, setCode] = useState('add')

  useEffect(() => {
    if (code === 'add') {
      setCount(c => c + 1)
    } else {
      setCount(c => c - 1)
    }
  }, [code, setCount])

  const keyDown = e => {
    if (e.keyCode === 32) {
      console.log('space~')
      if (code === 'add') {
        setCode('minus')
      } else {
        setCode('add')
      }
    }
  }

  const handleChange = e => {
    setValue(e.target.value)
  }

  return (
    <div>
      <div>{count}</div>
      <br />
      <input onKeyDown={keyDown} value={value} onChange={handleChange} />
    </div>
  )
}

export default App

Текущее поведение

элемент ввода не может получить пробел

Ожидаемое поведение

элемент ввода может получать пробел

DOM Unconfirmed Bug

Все 3 Комментарий

Похоже, что обновление состояния в обработчике onKeyDown прерывает событие и предотвращает вызов обработчика onChange . Поскольку контролируемые компоненты показывают только то, что вы им говорите (состояние value в вашем примере), это приводит к тому, что пробел никогда не появляется.

Мне это кажется ошибкой во взаимодействии этих двух событий.

cc @trueadm @gaearon за их опыт в системе событий

Привет, я здесь новенький, пытаюсь изучить архитектуру React.

@bvaughn Вот кое-что, что я нашел о проблеме.

Событие onKeyDown не блокирует onChange а срабатывает раньше, чем изменяется ввод. По сути, onKeyDown идет раньше, чем onChange потому что ввод текста происходит в onKeyUp .

В этом конкретном случае проблема заключается в том, что внутри keyDown , когда setCode вызывает, на первом dispatchAction _Fiber_ есть memoizedState без нового символа (очевидно) и e который будет добавлен. В конце концов, он добавляет на вход новый символ. Затем useEffect() снова запускает dispatchAction в _ той же очереди_ (не уверен, что это очередь), но он ничего не знает об обновленных свойствах. В результате, когда он commitRoot , он восстанавливает pendingProps которые такие же, как и в memoizedState . Короче говоря, символ (пробел в этом примере) добавляется, а затем сразу восстанавливается старое состояние без нового символа (пробела), поэтому похоже, что он ничего не добавляет.

В качестве примера того, что _same queue_ (?) Не используется, этот конкретный пример:

useEffect(() => {
    if (code === 'add') {
      setCount(c => c + 1)
    } else {
      setCount(c => c - 1)
    }
  }, [code, setCount])

можно изменить на:

useEffect(() => {
    if (code === 'add') {
      setTimeout(() => setCount(c => c + 1), 0)
    } else {
      setTimeout(() => setCount(c => c - 1), 0)
    }
  }, [code, setCount])

и это будет работать.

Надеюсь, это имеет смысл.

Возникла та же проблема, заключив мой вызов setState в setTimeout в обработчике keydown, исправив его.

Мне любопытно узнать, должно ли это работать по-другому под капотом в React, или эта ошибка указывает на более серьезную ошибку или плохой шаблон со стороны разработчика?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги