React useEffect 添加一个事件侦听器以在每次输入更改时引用
React useEffect adds an event listener to ref on every input change
采用以下组件:
function MyComponent() {
const [ inputValue, setInputValue ] = useState( '' );
const [ submittedValue, setSubmittedValue ] = useState( '' );
const inputRef = useRef<HTMLInputElement>( null );
useEffect( () => {
const inputElement = inputRef.current;
const handleEvent = ( event ) => {
if ( event.key === 'Enter' ) {
event.preventDefault();
setSubmittedValue( inputValue );
}
};
console.log( 'adding listener' );
inputElement?.addEventListener( 'keydown', handleEvent );
return () => inputElement?.removeEventListener( 'keydown', handleEvent );
}, [ inputValue ] );
return (
<>
<input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
<p>{submittedValue}</p>
</>
);
}
该组件可让您输入受控输入,然后按 'Enter' 键“提交”当前输入值。提交的值应显示在输入下方并保持静态,直到再次“提交”。
这按预期工作。 问题是每次击键都会添加一个 'keydown' 侦听器。
预期结果:'adding listener' 在装载
上打印一次
实际结果:'adding listener' 每次击键时打印
我明白为什么。在每次击键时,onChange
运行,运行 setInputValue
,这会更改 useEffect
的依赖列表,这会再次添加侦听器。
解决此问题的最简单方法是将 onKeyDown={handleEvent}
添加到 input
。但这太容易了。
我之所以走到这一步,是因为我使用的遗留 Input
元素没有实现 onKeyDown
有没有办法在不使用的情况下使用onKeyDown
达到预期的结果?
由于您还删除了每次击键时的事件侦听器,我认为您现在所做的非常好 - 根据需要,每个事件只触发一次处理程序,并且处理程序将在组件卸载,这很好。频繁添加和删除侦听器可能 感觉 不好,但老实说,它在 99% 的情况下不会引起任何问题,而且它有效,所以可能不值得担心。
如果您真的不想多次添加侦听器,我想您可以从 ref 中检索值,而不需要 inputValue
上的 up-to-date 闭包。
function MyComponent() {
const [ inputValue, setInputValue ] = React.useState( '' );
const [ submittedValue, setSubmittedValue ] = React.useState( '' );
const inputRef = React.useRef( null );
React.useEffect( () => {
const inputElement = inputRef.current;
const handleEvent = ( event ) => {
if ( event.key === 'Enter' ) {
event.preventDefault();
setSubmittedValue( inputRef.current.value );
}
};
inputElement.addEventListener( 'keydown', handleEvent );
return () => inputElement.removeEventListener( 'keydown', handleEvent );
}, [] );
return (
<React.Fragment>
<input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
<p>{submittedValue}</p>
</React.Fragment>
);
}
ReactDOM.render(<MyComponent />, document.querySelector('.react'));
<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 class='react'></div>
采用以下组件:
function MyComponent() {
const [ inputValue, setInputValue ] = useState( '' );
const [ submittedValue, setSubmittedValue ] = useState( '' );
const inputRef = useRef<HTMLInputElement>( null );
useEffect( () => {
const inputElement = inputRef.current;
const handleEvent = ( event ) => {
if ( event.key === 'Enter' ) {
event.preventDefault();
setSubmittedValue( inputValue );
}
};
console.log( 'adding listener' );
inputElement?.addEventListener( 'keydown', handleEvent );
return () => inputElement?.removeEventListener( 'keydown', handleEvent );
}, [ inputValue ] );
return (
<>
<input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
<p>{submittedValue}</p>
</>
);
}
该组件可让您输入受控输入,然后按 'Enter' 键“提交”当前输入值。提交的值应显示在输入下方并保持静态,直到再次“提交”。
这按预期工作。 问题是每次击键都会添加一个 'keydown' 侦听器。
预期结果:'adding listener' 在装载
上打印一次
实际结果:'adding listener' 每次击键时打印
我明白为什么。在每次击键时,onChange
运行,运行 setInputValue
,这会更改 useEffect
的依赖列表,这会再次添加侦听器。
解决此问题的最简单方法是将 onKeyDown={handleEvent}
添加到 input
。但这太容易了。
我之所以走到这一步,是因为我使用的遗留 Input
元素没有实现 onKeyDown
有没有办法在不使用的情况下使用onKeyDown
达到预期的结果?
由于您还删除了每次击键时的事件侦听器,我认为您现在所做的非常好 - 根据需要,每个事件只触发一次处理程序,并且处理程序将在组件卸载,这很好。频繁添加和删除侦听器可能 感觉 不好,但老实说,它在 99% 的情况下不会引起任何问题,而且它有效,所以可能不值得担心。
如果您真的不想多次添加侦听器,我想您可以从 ref 中检索值,而不需要 inputValue
上的 up-to-date 闭包。
function MyComponent() {
const [ inputValue, setInputValue ] = React.useState( '' );
const [ submittedValue, setSubmittedValue ] = React.useState( '' );
const inputRef = React.useRef( null );
React.useEffect( () => {
const inputElement = inputRef.current;
const handleEvent = ( event ) => {
if ( event.key === 'Enter' ) {
event.preventDefault();
setSubmittedValue( inputRef.current.value );
}
};
inputElement.addEventListener( 'keydown', handleEvent );
return () => inputElement.removeEventListener( 'keydown', handleEvent );
}, [] );
return (
<React.Fragment>
<input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
<p>{submittedValue}</p>
</React.Fragment>
);
}
ReactDOM.render(<MyComponent />, document.querySelector('.react'));
<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 class='react'></div>