「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > React: 古いクロージャ

React: 古いクロージャ

2024 年 9 月 2 日に公開
ブラウズ:440

この投稿では、useState フック React アプリでクロージャーを作成する方法を示します。

クロージャとは何かについては説明しません。このトピックに関するリソースは数多くあり、繰り返しになることは避けたいからです。 @imranabdulmalik によるこの記事を読むことをお勧めします。

要するに、クロージャは (Mozilla から):

です。

...一緒にバンドルされた (囲まれた) 関数とその周囲の状態 (語彙環境) への参照の組み合わせ。言い換えれば、クロージャを使用すると、内部関数から外部関数のスコープにアクセスできるようになります。 JavaScript では、関数が作成されるたびに、関数の作成時にクロージャが作成されます.

語彙環境という用語に慣れていない場合に備えて、@soumyadey によるこの記事、またはこの記事を読むことができます。

問題

React アプリケーションでは、useState フックで作成されたコンポーネントの状態に属する変数のクロージャーを誤って作成してしまう可能性があります。これが発生すると、古いクロージャの問題に直面することになります。つまり、状態の古い値を参照すると、その間に値が変更され、関連性が低くなります。

]

POC

Demo React アプリケーションを作成しました。その主な目的は、setTimeout メソッドのコールバック内のクロージャで閉じることができる (状態に属する) カウンターをインクリメントすることです。

つまり、このアプリは次のことができます:

  • カウンタの値を表示
  • カウンタを 1 増やす
  • 5 秒後にカウンターを 1 つ増やすタイマーを開始します。
  • カウンターを 10 増やす

次の図では、カウンターが 0 の、アプリの初期 UI 状態が示されています。

React: stale closure

カウンターの閉鎖を 3 つのステップでシミュレートします:

  1. カウンタを 1 増やす

React: stale closure

  1. 5 秒後に 1 ずつ増加するタイマーを開始します

React: stale closure

  • タイムアウトがトリガーされる前に 10 ずつ増加します React: stale closure

5 秒後、カウンターの値は 2 になります。

React: stale closure

カウンターの期待値は 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 (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    解決

    この解決策は、レンダリングに必要のない値を参照できる 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)
    

    リソース

    • Dmitri Pavlutin React フックを使用するときは古いクロージャに注意する
    • Imran Abdulmalik JavaScript でクロージャをマスターする: 包括的なガイド
    • JavaScript の Keyur Paralkar 語彙スコープ – 初心者ガイド
    • React での Souvik Paul の古いクロージャー
    • Soumya Dey JavaScript における語彙のスコープとクロージャの理解
    • スバシュ・マハパトラ スタックオーバーフロー
    リリースステートメント この記事は次の場所に転載されています: https://dev.to/animusna/react-stale-closure-81a?1 侵害がある場合は、[email protected] に連絡して削除してください。
    最新のチュートリアル もっと>

    免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

    Copyright© 2022 湘ICP备2022001581号-3