この投稿では、useState フック React アプリでクロージャーを作成する方法を示します。
クロージャとは何かについては説明しません。このトピックに関するリソースは数多くあり、繰り返しになることは避けたいからです。 @imranabdulmalik によるこの記事を読むことをお勧めします。
要するに、クロージャは (Mozilla から):
です。...一緒にバンドルされた (囲まれた) 関数とその周囲の状態 (語彙環境) への参照の組み合わせ。言い換えれば、クロージャを使用すると、内部関数から外部関数のスコープにアクセスできるようになります。 JavaScript では、関数が作成されるたびに、関数の作成時にクロージャが作成されます.
語彙環境という用語に慣れていない場合に備えて、@soumyadey によるこの記事、またはこの記事を読むことができます。
React アプリケーションでは、useState フックで作成されたコンポーネントの状態に属する変数のクロージャーを誤って作成してしまう可能性があります。これが発生すると、古いクロージャの問題に直面することになります。つまり、状態の古い値を参照すると、その間に値が変更され、関連性が低くなります。
]Demo React アプリケーションを作成しました。その主な目的は、setTimeout メソッドのコールバック内のクロージャで閉じることができる (状態に属する) カウンターをインクリメントすることです。
つまり、このアプリは次のことができます:
次の図では、カウンターが 0 の、アプリの初期 UI 状態が示されています。
カウンターの閉鎖を 3 つのステップでシミュレートします:
5 秒後、カウンターの値は 2 になります。
カウンターの期待値は 12 であるはずですが、2 が得られます。
これが発生する理由は、setTimeout に渡されるコールバック内で カウンタのクロージャ を作成し、タイムアウトがトリガーされたときにそのカウンタから開始してカウンタを設定したためです。古い値 (1 でした)。
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
アプリ コンポーネントの完全なコードに従います。
function App() { const [counter, setCounter] = useState(0) const timeOutInSeconds: number = 5 const [startTimeout, setStartTimeout] = useState (false) const [timeoutInProgress, setTimeoutInProgress] = useState (false) const [logs, setLogs] = useState >([]) useEffect(() => { if (startTimeout && !timeoutInProgress) { setTimeoutInProgress(true) setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`]) setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000); } }, [counter, startTimeout, timeoutInProgress]) function renderLogs(): React.ReactNode { const listItems = logs.map((log, index) => {log} ); return{listItems}
; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
この解決策は、レンダリングに必要のない値を参照できる useRef フックの使用に基づいています。
そこで、App コンポーネントに追加します:
const currentCounter = useRef(counter)
次に、setTimeout のコールバックを以下のように変更します。
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(currentCounter.current 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
現在の値をインクリメントする前にログに記録するため、コールバックはカウンター値を読み取る必要があります。
値を読み取る必要がない場合は、関数表記を使用してカウンターを更新するだけでカウンターのクローズを回避できます。
seCounter(c => c 1)
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3