react setStateの詳細な説明

react setStateの詳細な説明

setState は同期ですか、それとも非同期ですか?

カスタム合成イベントと React フック関数で状態を非同期的に更新する

カスタムクリックイベントのsetStateを例に挙げます

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  ハンドルクリック = () => {
    this.setState({
      カウント: this.state.count + 1
    });
    this.setState({
      カウント: this.state.count + 1
    });
    this.setState({
      カウント: this.state.count + 1
    });
    console.log(この状態の数);
  }
  与える() {
    戻る (
      <div style={{ 幅: '100px', 高さ: '100px', 背景色: "黄色" }}>
          {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

一度クリックすると、this.state.count の最終的な印刷結果は 1 になり、ページには 2 が表示されます。この現象から、3 つの setState のうち最後の setState のみが有効であり、最初の 2 つの setState は効果がないことがわかります。なぜなら、最初の setState を +3 に変更すると、count の印刷結果は 1 になり、表示結果は 2 になり、変化がないからです。また、カウント結果を取得するための同期はありません。

この時点で、setState の 2 番目のパラメータを通じて更新された状態を取得するようにコードを調整できます。

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  ハンドルクリック = () => {
    this.setState({
      カウント: this.state.count + 3
    }, () => {
      console.log('1', this.state.count)
    });
    this.setState({
      カウント: this.state.count + 1
    }, () => {
      console.log('2', this.state.count);
    });
    this.setState({
      カウント: this.state.count + 1
    }, () => {
      console.log('3', this.state.count);
    });
    console.log(この状態の数);
  }
  与える() {
    戻る (
      <div style={{ 幅: '100px', 高さ: '100px', 背景色: "黄色" }}>
          {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

このとき、一度クリックすると、3 つの setState コールバック関数の印刷結果は次のようになります。

1
1: 2
22
3: 2

まず、最後の行は 1 を直接出力します。そして、setState のコールバックでは、印刷される結果はすべて最新の更新 2 になります。最初の 2 回の setState 呼び出しは有効になりませんでしたが、2 番目のパラメーターには 2 が引き続き出力されます。

このとき、setState の第一パラメータを関数に置き換え、関数の第一パラメータを通じて更新前の状態を取得できるようになります。

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  ハンドルクリック = () => {
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    });
    console.log(この状態の数);
  }
  与える() {
    戻る (
      <div style={{ 幅: '100px', 高さ: '100px', 背景色: "黄色" }}>
          {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

このとき、印刷結果は 1 ですが、ページに表示されるカウントは 4 です。 setState がパラメータを渡して状態を更新する場合、複数の setState 更新は最後の更新だけでなく、複数の更新に有効になることがわかります。

次に、2 番目の関数でどれだけの count が印刷されるかを見てみましょう。

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  ハンドルクリック = () => {
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('1', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('2', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('3', this.state.count);
    });
    console.log(この状態の数);
  }
  与える() {
    戻る (
      <div style={{ 幅: '100px', 高さ: '100px', 背景色: "黄色" }}>
          {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

このとき、一度クリックすると、3つのsetStateコールバック関数での印刷結果は以下のようになります。ご想像のとおり、ページ表示結果も4です。

1
1:4
24
3:4

上記のコードをcomponentDidMountに入力すると、出力結果は上記と同じになります。

なぜなら、カスタム合成イベントとフック関数では、状態の更新が非同期であることがわかるからです。

ネイティブイベントとsetTimeoutで状態を同期的に更新する

setTimeoutのsetStateを例に挙げる

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  コンポーネントマウント() {
    タイムアウトを設定する(() => {
      this.setState({
        カウント: this.state.count + 1
      }, () => {
        console.log('1:', this.state.count);
      });
      this.setState({
        カウント: this.state.count + 1
      }, () => {
        console.log('2:', this.state.count);
      });
      this.setState({
        カウント: this.state.count + 1
      }, () => {
        console.log('3:', this.state.count);
      });
      console.log(この状態の数);
    }, 0);
  }
  与える() {
    戻る (
      <div 
        スタイル={{ 
          幅: '100px'、 
          高さ: '100px'、 
          背景色: "黄色" 
        }}>
          {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

現時点で印刷された結果は次のとおりです。

1: 2
23
3:4
4

setState の最初のパラメータを関数に置き換えます。

コンポーネントマウント() {
  タイムアウトを設定する(() => {
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('1', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('2', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('3', this.state.count);
    });
    console.log(この状態の数);
  }, 0);
}

印刷結果は上記と同じです。

状態は完全に制御可能だと感じますか? setTimeout では、複数の setState 呼び出しが有効になり、更新された状態は各 setState の 2 番目のパラメータで取得できます。

同様に、ネイティブ イベントの出力結果は setTimeout の出力結果と一致し、同期されます。

React をインポートします。{ コンポーネント } から 'react' をインポートします。
クラス Test は Component を拡張します {
  コンストラクタ(props) {
    スーパー(小道具);
    この状態 = {
      カウント: 1
    };
  }
  コンポーネントマウント() {
    document.body.addEventListener('click', this.handleClick, false);
  }
  コンポーネントのマウントを解除します(){
    document.body.removeEventListener('click', this.handleClick, false);
  }
  ハンドルクリック = () => {
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('1', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('2', this.state.count);
    });
    this.setState((前の状態、props) => {
      戻り値: 前の状態のカウント + 1 }
    }, () => {
      console.log('3', this.state.count);
    });
    console.log(この状態の数);
  }
  与える() {
    戻る (
      <div
        スタイル={{ 
          幅: '100px'、 
          高さ: '100px'、 
          背景色: "黄色" 
        }}
      >
        {この州数}
      </div>
    )
  }
}
デフォルトのテストをエクスポートします。

setState関連のソースコード

以下のコードはすべてreact17.0.2バージョンのものです

ディレクトリ ./packages/react/src/ReactBaseClasses.js

関数 Component(props, context, updater) {
  プロパティ
  this.context = コンテキスト;
  // コンポーネントに文字列参照がある場合は、後で別のオブジェクトを割り当てます。
  this.refs = 空のオブジェクト;
  // デフォルトのアップデータを初期化しますが、実際のアップデータは
  // レンダラー。
  this.updater = アップデーター || ReactNoopUpdateQueue;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  不変
    partialState の型 === 'オブジェクト' ||
      partialState の type === 'function' ||
      部分状態 == null、
    'setState(...): 更新する状態変数のオブジェクトまたは' +
      '状態変数のオブジェクトを返す関数。',
  );
  this.updater.enqueueSetState(this、partialState、コールバック、'setState');
};

setState は 2 つのパラメータを受け取ることができます。最初のパラメータはオブジェクト、関数、null、または undefined にすることができ、エラーはスローされません。以下の this.updater.enqueueSetState メソッドを実行します。 enqueueSetState をグローバルに検索し、2 つのディレクトリでこの変数を見つけます。

まず、最初のディレクトリ セット:

ディレクトリ ./packages/react/src/ReactNoopUpdateQueue.js の 100 行目の enqueueSetState メソッドでは、パラメーターは this、初期化された状態、コールバック、および文字列 setState です。this は現在の React インスタンスを参照します。

enqueueSetState: 関数(
  パブリックインスタンス、
  部分的な状態、
  折り返し電話、
  発信者名、
){
  warnNoop(publicInstance, 'setState');
}

次に、 warnNoop メソッドを見てみましょう。

const didWarnStateUpdateForUnmountedComponent = {};

関数 warnNoop(publicInstance, callerName) {
  __DEV__ の場合 {
    const コンストラクター = publicInstance.constructor;
    const コンポーネント名 =
      (コンストラクター && (コンストラクター.displayName || コンストラクター.name)) ||
      'ReactClass';
    const warningKey = `${componentName}.${callerName}`;
    if (didWarnStateUpdateForUnmountedComponent[警告キー]) {
      戻る;
    }
    コンソール.エラー(
      「まだマウントされていないコンポーネントでは %s を呼び出すことはできません。」 +
        「これは何も起こりませんが、アプリケーションにバグがあることを示している可能性があります。」 +
        '代わりに、`this.state`に直接割り当てるか、`state = {};`を定義します' +
        '%s コンポーネント内の目的の状態を持つクラス プロパティ。',
      発信者名、
      コンポーネント名、
    );
    マウントされていないコンポーネントに対してWarnStateUpdateを実行しました[警告キー] = true;
  }
}

このコードは、didWarnStateUpdateForUnmountedComponent オブジェクトに属性を追加することと同じです。属性のキーは、現在 setState.setState になっている React コンポーネントです。この属性が存在する場合は、それが返されます。この属性が存在しないか、この属性の値が false の場合は、この属性の値は true に設定されます。

別のディレクトリを見てみましょう:

ディレクトリ ./react-reconciler/src/ReactFiberClassComponent.new.js および ReactFiberClassComponent.old.js

const クラスコンポーネントアップデート = {
  enqueueSetState(inst、ペイロード、コールバック) {
    const ファイバー = getInstance(inst);
    定数イベント時間 = requestEventTime();
    定数レーン = requestUpdateLane(ファイバー);

    定数 update = createUpdate(eventTime, レーン);
    update.payload = ペイロード;
    if (コールバック !== 未定義 && コールバック !== null) {
      __DEV__ の場合 {
        無効なコールバックが発生した場合に警告します(コールバック、'setState');
      }
      update.callback = コールバック;
    }

    enqueueUpdate(ファイバー、更新、レーン);
    const ルート = scheduleUpdateOnFiber(ファイバー、レーン、イベント時間);
    ルートが null の場合
      entangleTransitions(ルート、ファイバー、レーン);
    }

    __DEV__ の場合 {
      デバッグトレースを有効にする場合
        if (fiber.mode & DebugTracingMode) {
          const name = getComponentNameFromFiber(fiber) || '不明';
          logStateUpdateScheduled(名前、レーン、ペイロード);
        }
      }
    }

    スケジューリングプロファイラーを有効にする場合
      markStateUpdateScheduled(ファイバー、レーン);
    }
  }
}

主な機能は enqueueUpdate です。

ディレクトリ ./react-reconciler/src/ReactUpdateQueue.new.js および ReactUpdateQueue.old.js

エクスポート関数 enqueueUpdate<State>(
  繊維: 繊維、
  更新: Update<状態>、
  レーン: レーン、
){
  定数 updateQueue = fiber.updateQueue;
  更新キューが null の場合
    // ファイバーがマウント解除されている場合にのみ発生します。
    戻る;
  }

  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;

  if (isInterleavedUpdate(ファイバー、レーン)) {
    const インターリーブ = sharedQueue.interleaved;
    if (インターリーブ === null) {
      // これは最初の更新です。循環リストを作成します。
      更新.next = 更新;
      // 現在のレンダリングの終了時に、このキューのインターリーブ更新は
      // 保留キューに転送されます。
      共有キューをプッシュします。
    } それ以外 {
      インターリーブされた
      interleaved.next = 更新;
    }
    sharedQueue.interleaved = 更新;
  } それ以外 {
    const pending = sharedQueue.pending;
    if (保留中 === null) {
      // これは最初の更新です。循環リストを作成します。
      更新.next = 更新;
    } それ以外 {
      更新.next = pending.next;
      pending.next = 更新;
    }
    sharedQueue.pending = 更新;
  }

  __DEV__ の場合 {
    もし (
      現在処理中のキュー === 共有キュー &&
      !didWarnUpdateInsideUpdate
    ){
      コンソール.エラー(
        '更新 (setState、replaceState、または forceUpdate) がスケジュールされました ' +
          '更新関数内から。更新関数は純粋でなければなりません。' +
          ' 副作用はありません。componentDidUpdate または ' + の使用を検討してください。
          '折り返し電話。'、
      );
      更新中に警告を発した = true;
    }
  }
}

これを見ると、このメソッドはこの更新の更新を更新キューに追加しますが、このバージョンでは isBatchingUpdates プロパティが見つからないことがわかります。 React Fiber への変更はかなり大きいようですので、とりあえずここで止めて、何か新しいことが見つかったら追加していきます。

要約する

  • カスタム合成イベントと React フック関数で状態を非同期的に更新する
  • ネイティブイベントとsetTimeoutで状態を同期的に更新する

以上が react setState の詳しい説明です。 react setState についてさらに詳しく知りたい方は、123WORDPRESS.COM 内の他の関連記事もぜひご覧ください!

以下もご興味があるかもしれません:
  • React Stateの原則を深く理解する
  • Reactの状態の理解についての簡単な分析
  • reactにおけるstateの略語の詳細な説明
  • ReactのsetStateソースコードの詳細な研究
  • ReactのsetStateの動作メカニズムの詳細な理解
  • React コンポーネントの状態と setState() についてどれくらい知っていますか?

<<:  MySQLのデッドロックとログに関する詳細な説明

>>:  Docker を使って LEMP 環境を素早く構築する方法の例

推薦する

MySQL DATE_FORMAT関数の使用

タオバオが、ダブル11に最も多くの注文をした2人のユーザー、ユーザー1:「ショッピングの皇帝、陳哈哈...

vue3 でブロック崩しゲームを開発する方法をステップバイステップで教えます

序文vue3 を使った例をいくつか書いてみましたが、Vue3 のコンポジション API はよく設計さ...

HTML での非同期ファイルアップロードの例

コードをコピーコードは次のとおりです。 <form action="/hehe&qu...

Linux コマンドラインターミナルで画面を分割するための 2 つのツール

ターミナル分割画面ツールは2つあります: screen と tmux 1. 画面分割を使用する(上下...

Docker で TLS と CA 認証を有効にする方法

目次1. 証明書を生成する2. リモートを有効にする3. リモート接続3.1 Jenkins接続3....

CentOS IP接続ネットワーク実装プロセス図

1. システムにログインし、ディレクトリに入ります: cd /etc/sysconfig/netwo...

角度コンテンツ投影の詳細な説明

目次単一コンテンツ投影マルチコンテンツ投影単一条件のコンテンツ投影アプリ-人物-htmlアプリ担当者...

Nginx ロードバランシングの設定方法

目次Nginx 負荷分散構成Nginx 負荷分散戦略ポーリング(デフォルト)重さip_ハッシュ公正(...

Linux でユーザー アカウントをロックおよびロック解除する 3 つの方法

組織内で何らかのパスワード ポリシーがすでに実装されている場合は、この記事を読む必要はありません。た...

Windows 10 + mysql 8.0.11 zipインストールチュートリアルの詳細

準備する: MySQL 8.0 Windows zip パッケージのダウンロード アドレス: htt...

MySQL インデックス データ構造の詳細な分析

目次概要インデックスデータ構造バイナリツリー赤黒木BツリーB+ツリーハッシュ索引InnoDB インデ...

XHTML ブロックレベルタグの概要

* 住所 - 住所* blockquote - ブロック引用* center - 中央揃えブロック*...

Tencent Cloudでhive3.1.2を構築する方法を教えます

環境の準備操作を開始する前に、hadoop バージョンがインストールされていることを確認してください...

CSSに基づいてマウス入力の方向を決定する

以前、フロントエンド技術グループに所属していたとき、グループのメンバーが面接中に問題に遭遇したと言っ...

CentOS システムの rpm インストールと Nginx の設定

目次CentOS rpm のインストールと Nginx の設定導入rpm パッケージのインストールサ...