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>