ホームページ > 記事 > ウェブフロントエンド > React: 古いクロージャ
この投稿では、useState フック React アプリでクロージャを作成する方法を示します。
クロージャとは何かについては説明しません。このトピックについては多くのリソースがあり、繰り返しになりたくないからです。 @imranabdulmalik によるこの記事を読むことをお勧めします。
簡単に言うと、クロージャは (Mozilla より):
...周囲の状態 (語彙環境) への参照とバンドルされた (囲まれた) 関数の組み合わせ。言い換えれば、クロージャを使用すると、内部関数から外部関数のスコープにアクセスできるようになります。 JavaScript では、関数が作成されるたびに、関数の作成時にクロージャが作成されます.
語彙環境という用語に慣れていない場合に備えて、@soumyadey によるこの記事、またはこの記事を読むことができます。
React アプリケーションでは、useState フックで作成されたコンポーネントの状態に属する変数のクロージャーを誤って作成してしまう可能性があります。これが発生すると、古いクロージャの問題に直面することになります。つまり、状態の古い値を参照すると、その間に変更され、関連性が低くなります。
Demo React アプリケーションを作成しました。その主な目的は、setTimeout メソッドのコールバック内のクロージャで閉じることができる (状態に属する) カウンターをインクリメントすることです。
つまり、このアプリは次のことができます:
次の図では、カウンターがゼロになったアプリの初期 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)
以上がReact: 古いクロージャの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。