ReactのuseEffectクロージャの落とし穴についての簡単な説明

ReactのuseEffectクロージャの落とし穴についての簡単な説明

問題コード

useEffectによって発生したクロージャの問題コードを見てみましょう

定数 btn = useRef();
const [v, setV] = useState('');

使用効果(() => {
    クリックハンドルを () とします => {
        コンソールにログ出力します。
    }
    btn.current.addEventListener('click', clickHandle)
    
    戻り値 () => {
        btn.removeEventListener('click', clickHandle)
    }
}, []);
    
定数入力ハンドル = e => {
    setV(e.target.value)
}

戻る (
        <>
            <入力値={v} onChange={inputHandle} />
            <button ref={btn} >テスト</button>
        </>
    )

useEffect の依存関係配列は空なので、内部コードはページのレンダリングが完了した後に 1 回だけ実行され、ページが破棄されたときにもう一度実行されます。このとき、入力ボックスに任意の文字を入力してテスト ボタンをクリックすると、出力は空になります。その後、どのような文字を入力して再度テスト ボタンをクリックしても、出力結果は空のままです。

なぜこのようなことが起こるのでしょうか?実際、それは閉鎖によって引き起こされます。

原因

関数のスコープは、関数が定義されるときに決定されます。

btn のクリック イベントを登録する場合、スコープは次のようになります。

現時点では、アクセス可能な自由変数 v はまだ null です。クリックイベントがトリガーされると、クリックコールバック関数が実行されます。このとき、まず実行コンテキストが作成され、スコープチェーンが実行コンテキストにコピーされます。

  • 入力ボックスに文字が入力されていない場合、クリックしたときに表示されるvは元のvのままです。
  • 入力ボックスに文字が入力されると、setV が呼び出されて状態が変更され、ページがレンダリングをトリガーし、コンポーネントの内部コードが再実行されて v が再宣言され、v は元の v ではなくなります。クリック イベントのスコープ内の v は、古い v のままです。これらは 2 つの異なる v です。

シーンを生成する

  • イベントバインディング。たとえば、サンプル コードでは、ページの初期レンダリングが完了した後にイベントが 1 回だけバインドされます。たとえば、echarts を使用する場合、useEffect で echarts のインスタンスが取得され、イベントがバインドされます。
  • タイマー。ページが読み込まれた後にタイマーを登録すると、タイマー内の関数で同様のクローズの問題も発生します。

解決

この閉鎖問題に対する 5 つの解決策は次のとおりです。

1. 代入によってvを直接変更し、vを変更するメソッドをuseCallbackでラップする

v を変更するメソッドを useCallback でラップします。useCallback でラップされた関数はキャッシュされます。依存関係の配列が空なので、ここで直接割り当てによって変更された v は古い v です。setState は状態を変更する公式に推奨される方法であるため、この方法は推奨されません。ここでは、setV は再レンダリングをトリガーするためだけに使用されています。

// 直接変更しやすいように、v の宣言を const から var に変更します。var [v, setV] = useState('');

const inputHandle = useCallback(e => {
    {値} = e.targetとする
    v = 値
    setV(値)
}, [])

2. useEffectの依存関係にvを追加する

これは、ほとんどの人が最初に思いつく解決策かもしれません。v は古いので、v が更新されるたびにイベントを再登録すればいいのではないでしょうか。しかし、これでは v が更新されるたびに再登録が必要になります。理論上は、1 回だけ登録すればよいイベントが複数回登録されることになります。

3. vの再宣言を避ける

let または var を使用して v の代わりに変数を宣言し、setState 関連関数を使用してレンダリングをトリガーする代わりに、変数を直接変更します。この方法では、変数は再宣言されず、クリック コールバック関数で「最新の」値を取得できます。ただし、この方法は推奨されません。この例では、再レンダリングがないため、入力コンポーネントには常に空の値が表示され、期待される操作を満たしていません。

4. useStateの代わりにuseRefを使用する

定数 btn = useRef();
定数vRef = useRef('');
定数[v, setV] = useStat('');

使用効果(() => {
    クリックハンドルを () とします => {
        console.log('v:', vRef.current);
    }
    btn.current.addEventListener('click', clickHandle)
    
    戻り値 () => {
        btn.removeEventListener('click', clickHandle)
    }
}, []);

定数入力ハンドル = e => {
    {値} = e.targetとする
    vRef.current = 値
    setV(値)
}

戻る (
        <>
            <入力値={v} onChange={inputHandle} />
            <button ref={btn} >テスト</button>
        </>
    )

useRef ソリューションが効果的な理由は、入力の変更ごとに vRef オブジェクトの現在のプロパティが変更され、vRef は再レンダリングされても常にその vRef であるためです。vRef はオブジェクトであるため、スタック メモリに格納されている変数の値はヒープ メモリ内のオブジェクトのアドレスであり、単なる参照です。オブジェクトの特定のプロパティのみが変更され、参照は変更されません。クリックイベントのスコープチェーンは常に同じvRefにアクセスします

5. vをオブジェクト型に置き換える

実際、useRef を使用する場合と同様に、オブジェクトである限り、特定の属性のみを変更しても、状態が指すアドレスは変更されません。

コードアドレス

テストコードを見るにはここをクリックしてください

これで、React useEffect クロージャの落とし穴に関するこの記事は終わりです。React useEffect クロージャに関するより関連性の高いコンテンツについては、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • React useEffect の理解と使用
  • React における useEffect と useLayoutEffect の違い

<<:  Linux が Sudo 権限昇格の脆弱性を公開、どのユーザーでも root コマンドを実行可能

>>:  Dockerでパラメータ変数を外部から指定する方法

推薦する

WEB中国語フォントアプリケーションガイド

Web 上でフォントを使用することは、基本的なスキルであると同時に芸術でもあります。英語のフォントに...

Vue router-viewとrouter-linkの実装原理

使用 <div id="アプリ"> <router-link ...

JavaがMySQL 8.0に接続できない問題の解決策

この記事では、参考までにMySQL 8.0に接続できないJavaの問題をまとめて紹介します。具体的な...

MySQLデータベースの一般的な最適化操作のまとめ(経験共有)

序文データ中心のアプリケーションの場合、データベースの品質はプログラムのパフォーマンスに直接影響する...

Workermanはmysql接続プールのサンプルコードを書きます

まず、接続プールを使用する理由と、接続プールによってどのような問題が解決できるかを理解する必要があり...

MySQLでよく使われる4つのストレージエンジンについて簡単に説明します。

よく使われる4つのMySQLエンジンの紹介(1):MyISAMストレージエンジン:トランザクションや...

MySQL のソートとページング (order by と limit) と既存の落とし穴

並べ替えクエリ (order by)電子商取引の場合: 今日完了したすべての注文を表示し、取引金額に...

@font-face を使用して Web ページに特殊文字を実装する (カスタム フォントを作成する)

数日前、CSS を使用して三角形の矢印を実装する方法について記事を書きました。 目的の効果は達成され...

Linux システムの busybox に mkfs.vfat コマンドを移植する

オーディオおよびビデオ ファイルを保存するためのディスク寿命を延ばすには、ディスクをフォーマットする...

JavaScriptでカレンダー効果を素早く実装

この記事では、カレンダー効果を素早く実現するためのJavaScriptの具体的なコードを例として紹介...

Node.js の非同期ジェネレータと非同期反復の詳細な説明

序文ジェネレーター関数は、async/await が導入される前から JavaScript に存在し...

Vueナンバープレート入力コンポーネントの使い方の詳しい説明

参考までに、シンプルなナンバープレート入力コンポーネント(vue)です。具体的な内容は次のとおりです...

MySQL が my.cnf を読み込む順序の詳細

目次MySQLがmy.cnfを読み込む順序1. mysql.server の起動方法2. mysql...