我有两个input
,其值反映了计数器的状态。
第一个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>
问题是:按↑↓键会在setLazyValue()
之前触发onComplete(e)
函数——我想是因为它是异步的——失去与状态的同步。
出于同样的原因,按Esc会模糊input
,然后再将其值重置为initialValue
。
我知道setState()
类回调已被useEffect()
钩子取代,但是:
- 如何从
useEffect()
调用onComplete(e)
函数(像任何其他事件一样(? - 如何仅在按下↑↓键时执行此操作,而不是在所有其他键上执行此操作?
提前感谢您的帮助,这是一个交互式沙盒。
我建议将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>