フックを使用してイベントを登録する方法についてUdemyコースをフォローしています。インストラクターは以下のコードを提供しました。
const [userText, setUserText] = useState('');
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
};
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
});
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
今ではそれはうまくいきますが、これが正しい方法であると私は確信していません。その理由は、私が正しく理解すれば、再レンダリングのたびに、イベントが毎回登録および登録解除され続け、それが適切な方法であるとは思えないからです。
だから私は以下のuseEffect
フックに少し変更を加えました
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, []);
2番目の引数として空の配列を使用することにより、コンポーネントにエフェクトを1回だけ実行させ、componentDidMount
を模倣します。結果を試してみると、入力するすべてのキーで、追加するのではなく、上書きされるのは奇妙です。
期待していたsetUserText(${userText}${key}
);新しいタイプのキーを現在の状態に追加して新しい状態として設定するには、代わりに古い状態を忘れて新しい状態で書き換えます。
すべての再レンダリングでイベントを登録および登録解除するのは本当に正しい方法でしたか?
このようなシナリオに取り組む最善の方法は、イベントハンドラーで何をしているのかを確認することです。以前の状態を使用して状態を設定するだけの場合は、コールバックパターンを使用して、初期マウント時にのみイベントリスナーを登録するのが最善です。 callback pattern
( https://reactjs.org/docs/hooks-reference.html#usecallback )を使用しない場合、リスナー参照とそのレキシカルスコープがイベントリスナーですが、新しいレンダリングで新しい関数が更新されたクロージャーで作成されるため、ハンドラーでは更新された状態にできません
const [userText, setUserText] = useState('');
const handleUserKeyPress = useCallback(event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prevUserText => `${prevUserText}${key}`);
}
}, []);
useEffect(() => {
window.addEventListener('keydown', handleUserKeyPress);
return () => {
window.removeEventListener('keydown', handleUserKeyPress);
};
}, [handleUserKeyPress]);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
新しい答え:
_useEffect(() => {
function handlekeydownEvent(event) {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prevUserText => `${prevUserText}${key}`);
}
}
document.addEventListener('keyup', handlekeydownEvent)
return () => {
document.removeEventListener('keyup', handlekeydownEvent)
}
}, [])
_
setUserText
を使用する場合は、オブジェクトではなく関数を引数として渡します。prevUserText
は常に最新の状態になります。
古い答え:
これを試してください、それはあなたの元のコードと同じように動作します:
_useEffect(() => {
function handlekeydownEvent(event) {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${userText}${key}`);
}
}
document.addEventListener('keyup', handlekeydownEvent)
return () => {
document.removeEventListener('keyup', handlekeydownEvent)
}
}, [userText])
_
useEffect()
メソッドでは、userText
変数に依存しますが、2番目の引数内に配置しないため、userText
は常に最初の引数にバインドされます値_''
_と引数_[]
_。
このようにする必要はありません。2番目のソリューションが機能しない理由を知らせたいだけです。
ユースケースでは、useEffect
は変更を追跡するための依存関係配列を必要とし、依存関係に基づいて再レンダリングするかどうかを決定できます。依存関係配列をuseEffect
に渡すことを常にお勧めします。以下のコードをご覧ください。
useCallback
フックを導入しました。
const { useCallback, useState, useEffect } = React;
const [userText, setUserText] = useState("");
const handleUserKeyPress = useCallback(event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(prevUserText => `${prevUserText}${key}`);
}
}, []);
useEffect(() => {
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, [handleUserKeyPress]);
return (
<div>
<blockquote>{userText}</blockquote>
</div>
);
以前の状態を追跡する方法が必要です。 useState
は、現在の状態のみを追跡するのに役立ちます。 docs から、別のフックを使用して古い状態にアクセスする方法があります。
const prevRef = useRef();
useEffect(() => {
prevRef.current = userText;
});
これを使用するようにサンプルを更新しました。そしてそれはうまくいきます。
const { useState, useEffect, useRef } = React;
const App = () => {
const [userText, setUserText] = useState("");
const prevRef = useRef();
useEffect(() => {
prevRef.current = userText;
});
const handleUserKeyPress = event => {
const { key, keyCode } = event;
if (keyCode === 32 || (keyCode >= 65 && keyCode <= 90)) {
setUserText(`${prevRef.current}${key}`);
}
};
useEffect(() => {
window.addEventListener("keydown", handleUserKeyPress);
return () => {
window.removeEventListener("keydown", handleUserKeyPress);
};
}, []);
return (
<div>
<h1>Feel free to type!</h1>
<blockquote>{userText}</blockquote>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>