Как вызвать событие из компонента после установки его состояния с помощью перехватчиков React? - PullRequest
2 голосов
/ 07 февраля 2020

У меня есть два input s, значения которых отражают состояние счетчика.

Первый input устанавливает состояние немедленно при изменении его значения. Этот input может представлять другие части изменяющегося состояния приложения.

Вторая input часть компонента, которую я создал, с именем LazyInput. LazyInput не обновляет состояние немедленно , только когда оно размыто, что, на мой взгляд, повышает удобство использования. Этот ввод может представлять пользовательский ввод, например, установить таймер или положение чего-либо на экране.

Однако я бы хотел, чтобы LazyInput не вел себя "лениво" и фактически немедленно обновлял счетчик. только при изменении значения с помощью клавиш .

Код App (который включает в себя обычный input) является следующим:

const {Fragment, useState, useEffect} = React;

function LazyInput({ name, value, onComplete }) {
  const initialValue = value;
  const [lazyValue, setLazyValue] = useState(value);

  // Sync to state changed by the regular input
  useEffect(() => {
    setLazyValue(value);
  }, [value]);

  const handleKeyDown = e => {
    const { key, target } = e;
    switch (key) {
      case "ArrowUp": {
        setLazyValue(parseFloat(lazyValue) + 1);
        onComplete(e);
        break;
      }
      case "ArrowDown": {
        setLazyValue(parseFloat(lazyValue) - 1);
        onComplete(e);
        break;
      }
      case "Escape": {
        setLazyValue(initialValue);
        target.blur();
        break;
      }
      case "Enter": {
        target.blur();
        break;
      }
      default:
        return;
    }
  };
  return (
    <input
      name={name}
      value={lazyValue}
      onChange={e => setLazyValue(e.target.value)}
      onKeyDown={e => handleKeyDown(e)}
      onBlur={e => onComplete(e)}
    />
  );
}

function App() {
  const [counter, setCounter] = useState(0);

  function handleCounterChange(e) {
    setCounter(parseFloat(e.target.value));
  }

  return (
    <Fragment>
      <div>Counter: {counter}</div>
      <LazyInput
        name="counter"
        value={counter}
        onComplete={e => handleCounterChange(e)}
      />
      <input
        value={counter}
        type="number"
        onChange={e => handleCounterChange(e)}
      />
    </Fragment>
  );
}

ReactDOM.render(<App />, document.getElementById("app"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="app"></div>

Проблема заключается в следующем: нажатие клавиш вызывает функцию onComplete(e) перед setLazyValue() - я полагаю, потому что это асинхронно - потеря syn c с состоянием.

По той же причине нажатие Es c размывает input до сброса его значения на initialValue.

Я знаю, что обратный вызов класса setState() был заменен хуком useEffect() , но:

  1. Как вызвать функцию onComplete(e) - как и любое другое событие - из useEffect()?
  2. Как это сделать только тогда, когда ↓ Клавиши нажаты, но не для всех других клавиш?

Заранее спасибо за помощь, вот вам интерактивная песочница .

1 Ответ

2 голосов
/ 07 февраля 2020

Я бы рекомендовал передавать counter и setCounter до LazyInput, чтобы к ним можно было получить прямой доступ. useEffect может быть задан массив зависимостей, в котором он будет срабатывать только при изменении одной из его зависимостей. Однако useEffect не подходит для этой ситуации. Вы можете назначить counter как зависимость useEffect, но она будет срабатывать каждый раз, когда изменяется значение counter. useEffect может только наблюдать переменные, вы не можете передавать значения в него. Я предлагаю прочитать эту статью, чтобы узнать, как работает useEffect и когда его использовать.

https://overreacted.io/a-complete-guide-to-useeffect/

Мое решение проходит counter и setCounter до LazyInput. Если вы хотите, чтобы LazyInput сохранил свое собственное состояние, вы можете использовать useEffect для просмотра lazyValue. Когда lazyValue изменяется, counter должен быть обновлен.

Вот мое решение:

const {Fragment, useState, useEffect} = React;

function LazyInput({ name, counter, setCounter }) {
  const [initialValue] = useState(counter);

  const handleKeyDown = e => {
    const { key, target } = e;

    switch (key) {
      case "ArrowUp": {
        setCounter(counter + 1);
        break;
      }
      case "ArrowDown": {
        setCounter(counter - 1);
        break;
      }
      case "Escape": {
        setCounter(initialValue);
        target.blur();
        break;
      }
      case "Enter": {
        target.blur();
        break;
      }
      default:
        return;
    }
  };
  return (
    <input
      name={name}
      value={counter}
      onChange={e => setCounter(e.target.value)}
      onKeyDown={e => handleKeyDown(e)}
    />
  );
}

function App() {
  const [counter, setCounter] = useState(0);

  return (
    <Fragment>
      <div>Counter: {counter}</div>
      <LazyInput name="counter" counter={counter} setCounter={setCounter} />
      <input
        value={counter}
        type="number"
        onChange={e => setCounter(parseFloat(e.target.value))}
      />
    </Fragment>
  );
}

ReactDOM.render(<App />, document.getElementById("app"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>


<div id="app"></div>
...