React: 古いクロージャ

王林
リリース: 2024-08-21 06:19:02
オリジナル
468 人が閲覧しました

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

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

簡単に言うと、クロージャは (Mozilla より):

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

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

問題

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

POC

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

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

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

次の図では、カウンターがゼロになったアプリの初期 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 語彙スコープ – 初心者ガイド
    • Souvik Paul React の古いクロージャ
    • Soumya Dey JavaScript における語彙のスコープとクロージャーを理解する
    • Subash Mahapatra スタックオーバーフロー

    以上がReact: 古いクロージャの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    ソース:dev.to
    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート