React v17 ではイベントの仕組みが大きく変更されており、v16 とはかなり異なっていると思います。 この記事で分析した React のバージョンは 17.0.1 であり、アプリケーションは優先度関連の情報なしで ReactDOM.render を使用して作成されています。 原理React のイベントは、委任イベント (DelegatedEvent) と非委任イベント (NonDelegatedEvent) に分けられます。委任イベントが fiberRoot によって作成されると、ほぼすべてのイベント処理関数がルート ノードの DOM 要素にバインドされますが、非委任イベントでは、処理関数が DOM 要素自体にのみバインドされます。 同時に、React はイベントを discreteEvent、userBlockingEvent、continuousEvent の 3 つのタイプに分類します。これらは優先順位が異なり、イベント処理関数をバインドするときに異なるコールバック関数を使用します。 React イベントはネイティブ ベースで構築され、一連のバブリングとキャプチャ イベント メカニズムをシミュレートします。DOM 要素がイベントをトリガーすると、React のルート ノードにバインドされている処理関数にバブリングし、ターゲットを通じてイベントをトリガーした DOM オブジェクトと対応する Fiber ノードを取得します。Fiber ノードは上位の親にトラバースしてイベント キューを収集し、キューをトラバースしてキュー内の各 Fiber オブジェクトに対応するイベント処理関数をトリガーします。前方トラバーサルはバブリングをシミュレートし、後方トラバーサルはキャプチャをシミュレートするため、合成イベントはネイティブ イベントの後にトリガーされます。 Fiber オブジェクトに対応するイベント処理関数は、引き続き props に格納されます。コレクションは props から取得されるだけで、どの要素にもバインドされません。 ソースコード分析以下のソース コードは、イベント メカニズムのトリガー プロセスを明確にし、プロセスに関係のない複雑なコードを大量に削除することを目的とした、基本ロジックの簡単な分析のみです。 委任されたイベントバインディングこのステップは、ReactDOM.render が呼び出されたときに発生します。fiberRoot が作成されると、サポートされているすべてのイベントがルート ノードの DOM 要素でリッスンされます。 関数createRootImpl( コンテナ: コンテナ、 タグ: ルートタグ、 オプション: void | RootOptions, ){ // ... 定数ルートコンテナ要素 = container.nodeType === COMMENT_NODE ? container.parentNode : コンテナ; // サポートされているすべてのイベントをリッスンします listenToAllSupportedEvents(rootContainerElement); // ... } すべてのサポートされているイベントを聴くイベントをバインドすると、対応する eventName は allNativeEvents という名前の Set 変数を通じて取得されます。この変数はトップレベル関数で収集され、nonDelegatedEvents は定義済みの Set です。 エクスポート関数 listenToAllSupportedEvents(rootContainerElement: EventTarget) { allNativeEvents.forEach(domEventName => { // 委任を必要としないイベントを除外する if (!nonDelegatedEvents.has(domEventName)) { // バブル listenToNativeEvent( domイベント名、 間違い、 ((rootContainerElement: 任意): 要素), ヌル、 ); } // listenToNativeEvent をキャプチャする( domイベント名、 真実、 ((rootContainerElement: 任意): 要素), ヌル、 ); }); } ネイティブイベントを聴くlistenToNativeEvent 関数は、イベントをバインドする前に DOM 要素にイベント名をマークし、false と判断された場合にのみバインドします。 エクスポート関数 listenToNativeEvent( DOMイベント名: DOMイベント名、 isCapturePhaseListener: ブール値、 ルートコンテナ要素: イベントターゲット、 targetElement: 要素 | null、 イベントシステムフラグ?: イベントシステムフラグ = 0、 ): 空所 { ターゲットを rootContainerElement とします。 // ... // 現在の要素がリッスンするイベントを識別するために、DOM 要素にセットを格納します。const listenerSet = getEventListenerSet(target); // イベント識別キー、文字列連結処理 const listenerSetKey = getListenerSetKey( domイベント名、 キャプチャフェーズリスナー、 ); リスナーセットにリスナーセットキーがある場合 // キャプチャとしてマーク if (isCapturePhaseListener) { イベントシステムフラグ |= IS_CAPTURE_PHASE; } // イベントをバインド addTrappedEventListener( ターゲット、 domイベント名、 イベントシステムフラグ、 キャプチャフェーズリスナー、 ); // セットに追加 リスナーセット。リスナーセットキーを追加します。 } } トラップイベントリスナーを追加addTrappedEventListener 関数は、イベント名を通じて対応する優先度のリスナー関数を取得し、それを下位レベルの関数に渡してイベント バインディングを処理します。 このリスナー関数はクロージャー関数であり、targetContainer、domEventName、eventSystemFlags の 3 つの変数にアクセスできます。 関数 addTrappedEventListener( ターゲットコンテナ: イベントターゲット、 DOMイベント名: DOMイベント名、 イベントシステムフラグ: イベントシステムフラグ、 isCapturePhaseListener: ブール値、 isDeferredListenerForLegacyFBSupport?: ブール値、 ){ // 優先度に応じて対応するリスナーを取得します リスナー = createEventListenerWrapperWithPriority( ターゲットコンテナ、 domイベント名、 イベントシステムフラグ、 ); if (isCapturePhaseListener) { イベントキャプチャリスナーを追加します(ターゲットコンテナ、domEventName、リスナー)。 } それ以外 { ターゲットコンテナ、domEventName、リスナーにEventBubbleListenerを追加します。 } } addEventCaptureListener 関数と addEventBubbleListener 関数は、ネイティブの target.addEventListener を呼び出してイベントをバインドします。 このステップでは、イベント名を含むセットをループし、各イベントに対応する処理関数をルート ノードの DOM 要素にバインドします。 デリゲートイベントバインディングは不要委任を必要としないイベントには、メディア要素のイベントも含まれます。 エクスポートconst nonDelegatedEvents: Set<DOMEventName> = new Set([ 'キャンセル'、 '近い'、 '無効'、 '負荷'、 'スクロール'、 'トグル'、 ...メディアイベントタイプ、 ]); エクスポートconst mediaEventTypes: Array<DOMEventName> = [ 'アボート'、 「プレイできる」、 'プレイスルー可能'、 '期間変更'、 「空になった」 「暗号化」、 「終了」、 'エラー'、 'ロードされたデータ'、 'ロードされたメタデータ'、 'ロードスタート'、 '一時停止'、 '遊ぶ'、 「遊ぶ」、 '進捗'、 'レート変更'、 「求めた」、 「求めている」、 「行き詰まった」、 'つるす'、 'timeupdate'、 'ボリューム変更'、 '待っている'、 ]; 初期プロパティの設定setInitialProperties メソッドは、委任なしで DOM 要素自体に直接バインドし、スタイルといくつかの受信 DOM 属性も設定します。 エクスポート関数setInitialProperties( domElement: 要素、 タグ: 文字列、 rawProps: オブジェクト、 rootContainerElement: 要素 | ドキュメント、 ): 空所 { プロパティを設定します: オブジェクト; スイッチ (タグ) { // ... ケース「ビデオ」: ケース 'オーディオ': (i = 0 とします; i < mediaEventTypes.length; i++) { DOM要素のmediaEventTypes[i]をlistenToNonDelegatedEventとして取得します。 } プロパティ = 生のプロパティ; 壊す; デフォルト: プロパティ = 生のプロパティ; } // スタイルなどの DOM 属性を設定します... 初期DOMプロパティを設定する( タグ、 dom要素、 ルートコンテナ要素、 小道具、 isCustomComponentTag、 ); } スイッチでは、対応するイベントがさまざまな要素タイプに応じてバインドされます。ここでは、ビデオ要素とオーディオ要素の処理のみが残っています。これらは mediaEventTypes をトラバースして、イベントを DOM 要素自体にバインドします。 非委任イベントを聴くlistenToNonDelegatedEvent メソッドのロジックは、基本的に前のセクションの listenToNativeEvent メソッドと同じです。 エクスポート関数 listenToNonDelegatedEvent( DOMイベント名: DOMイベント名、 targetElement: 要素、 ): 空所 { 定数isCapturePhaseListener = false; リスナーセットは、ターゲット要素のイベントリスナーセットを取得します。 定数リスナーSetKey = getListenerSetKey( domイベント名、 キャプチャフェーズリスナー、 ); リスナーセットにリスナーセットキーがある場合 トラップイベントリスナーを追加します( ターゲット要素、 domイベント名、 IS_NON_DELEGATED、 キャプチャフェーズリスナー、 ); リスナーセット。リスナーセットキーを追加します。 } } イベント処理は DOM 要素自体にバインドされていますが、バインドされたイベント処理関数はコードで渡される関数ではなく、後続のトリガーは引き続き実行のために処理関数を収集することに注意してください。 イベントハンドライベント処理関数は、コードで渡される関数ではなく、React のデフォルトの処理関数を参照します。 この関数は createEventListenerWrapperWithPriority メソッドによって作成され、対応する手順は上記の addTrappedEventListener にあります。 優先度付きイベントリスナーラッパーを作成する関数createEventListenerWrapperWithPriority(をエクスポートする ターゲットコンテナ: イベントターゲット、 DOMイベント名: DOMイベント名、 イベントシステムフラグ: イベントシステムフラグ、 ): 関数 { // 組み込みマップからイベントの優先度を取得します。const eventPriority = getEventPriorityForPluginSystem(domEventName); リスナーラッパーを作成します。 // 優先度に応じて異なるリスナーを返す スイッチ (イベント優先度) { 離散イベントの場合: リスナーラッパー = dispatchDiscreteEvent; 壊す; UserBlockingEventの場合: リスナーラッパー = ディスパッチユーザーブロック更新; 壊す; 連続イベントの場合: デフォルト: リスナーラッパー = ディスパッチイベント; 壊す; } listenerWrapper.bind() を返します。 ヌル、 domイベント名、 イベントシステムフラグ、 ターゲットコンテナ、 ); } createEventListenerWrapperWithPriority 関数は、イベントの優先度に対応するリスナーを返します。これら 3 つの関数はすべて 4 つのパラメータを受け取ります。 関数fn( domイベント名、 イベントシステムフラグ、 容器、 ネイティブイベント、 ){ //... } 戻るときに、bind は 3 つのパラメータで渡されるため、返される関数は nativeEvent のみを受け取り、最初の 3 つのパラメータにアクセスできる処理関数になります。 実際には、dispatchEvent メソッドは、dispatchDiscreteEvent メソッドと dispatchUserBlockingUpdate メソッドによって内部的に呼び出されます。 ディスパッチイベントここでは多くのコードが削除されており、イベントをトリガーするコードのみが表示されています。 エクスポート関数dispatchEvent( DOMイベント名: DOMイベント名、 イベントシステムフラグ: イベントシステムフラグ、 ターゲットコンテナ: イベントターゲット、 ネイティブイベント: 任意のネイティブイベント、 ): 空所 { // ... // イベントをトリガーする attemptToDispatchEvent( domイベント名、 イベントシステムフラグ、 ターゲットコンテナ、 ネイティブイベント、 ); // ... } attemptToDispatchEvent メソッドは依然として多くの複雑なロジックを処理し、関数呼び出しスタックには複数のレイヤーがあります。これらはすべてスキップし、主要なトリガー関数のみを見ていきます。 プラグインのディスパッチイベントdispatchEventsForPlugins 関数は、イベントをトリガーする各レベルのノードに対応する処理関数、つまり実際に JSX に渡す関数を収集して実行します。 関数dispatchEventsForPlugins( DOMイベント名: DOMイベント名、 イベントシステムフラグ: イベントシステムフラグ、 ネイティブイベント: 任意のネイティブイベント、 targetInst: null | ファイバー、 ターゲットコンテナ: イベントターゲット、 ): 空所 { ネイティブイベントターゲットを取得します。 定数ディスパッチキュー: ディスパッチキュー = []; // バブリングをシミュレートするためのリスナーを収集する extractEvents( ディスパッチキュー、 domイベント名、 ターゲットInst、 ネイティブイベント、 ネイティブイベントターゲット、 イベントシステムフラグ、 ターゲットコンテナ、 ); //実行キュー processDispatchQueue(dispatchQueue, eventSystemFlags); } 抽出イベントextractEvents 関数は主に、さまざまな種類のイベントに対応する合成イベントを作成し、各レベルのノードのリスナーを収集して、バブリングまたはキャプチャをシミュレートします。 ここのコードは長くなっており、多くの無関係なコードが削除されています。 関数extractEvents( ディスパッチキュー: ディスパッチキュー、 DOMイベント名: DOMイベント名、 targetInst: null | ファイバー、 ネイティブイベント: 任意のネイティブイベント、 ネイティブイベントターゲット: null | イベントターゲット、 イベントシステムフラグ: イベントシステムフラグ、 ターゲットコンテナ: イベントターゲット、 ): 空所 { const reactName = topLevelEventsToReactNames.get(domEventName); SyntheticEventCtor を SyntheticEvent とします。 reactEventType: string = domEventName; とします。 // 異なるイベントに応じて異なる合成イベントを作成する switch (domEventName) { ケース 'キー押下': ケース 'keydown': ケース 'keyup': SyntheticEventCtor = SyntheticKeyboardEvent; 壊す; 'クリック'の場合: // ... 'マウスオーバー'の場合: SyntheticEventCtor = SyntheticMouseEvent; 壊す; ケース「ドラッグ」: // ... ケース「ドロップ」: SyntheticEventCtor = SyntheticDragEvent; 壊す; // ... デフォルト: 壊す; } // ... // 各レベルでリスナーを収集する const リスナー = シングルフェーズリスナーを蓄積する( ターゲットInst、 反応名、 ネイティブイベントタイプ、 キャプチャフェーズ中、 累積ターゲットのみ、 ); リスナーの長さが0より大きい場合 // 合成イベントを作成する const event = new SyntheticEventCtor( 反応名、 反応イベントタイプ、 ヌル、 ネイティブイベント、 ネイティブイベントターゲット、 ); ディスパッチキューをプッシュします({イベント、リスナー}); } } シングルフェーズリスナーを蓄積するassembleSinglePhaseListeners 関数は上位層を走査して、後でバブリングをシミュレートするために使用されるリストを収集します。 エクスポート関数accumulateSinglePhaseListeners( targetFiber: ファイバー | null、 reactName: 文字列 | null、 ネイティブイベントタイプ: 文字列、 inCapturePhase: ブール値、 累積ターゲットのみ: ブール値、 ): 配列<DispatchListener> { const captureName = reactName !== null ? reactName + 'Capture' : null; const reactEventName = inCapturePhase ? captureName: reactName; const リスナー: Array<DispatchListener> = []; インスタンスを targetFiber にします。 lastHostComponent を null にします。 // イベントをトリガーするファイバーノードをトラバースしてDOMとリスナーを収集します while (インスタンス !== null) { const {stateNode, タグ} = インスタンス; // HostComponents のみにリスナーがあります (つまり <div>) if (タグ === HostComponent && stateNode !== null) { 最後のホストコンポーネント = stateNode; (reactEventName !== null)の場合{ // ファイバー ノードの props から受信イベント リスナー関数を取得します。const listener = getListener(instance, reactEventName); if (リスナー != null) { リスナー.push({ 実例、 リスナー、 現在のターゲット: lastHostComponent、 }); } } } ターゲットのみを累積する場合 壊す; } // 上へ進むinstance = instance.return; } リスナーを返します。 } 最終的なデータ構造は次のようになります。 dispatchQueue のデータ構造は、[{event,listeners }] 型の配列です。 リスナーは、タイプ [{ currentTarget, instance, listener }] のレイヤーごとに収集されたデータです。 プロセスディスパッチキューdispatchQueue は processDispatchQueue 関数で走査されます。 エクスポート関数 processDispatchQueue( ディスパッチキュー: ディスパッチキュー、 イベントシステムフラグ: イベントシステムフラグ、 ): 空所 { const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0; (i = 0 とします; i < ディスパッチキューの長さ; i++) { const {イベント、リスナー} = dispatchQueue[i]; processDispatchQueueItemsInOrder(イベント、リスナー、inCapturePhase); } } dispatchQueue 内の各項目は、processDispatchQueueItemsInOrder 関数で走査され、実行されます。 プロセスディスパッチキューアイテムの順序関数 processDispatchQueueItemsInOrder( イベント: ReactSyntheticEvent、 ディスパッチリスナー: 配列<ディスパッチリスナー>, inCapturePhase: ブール値、 ): 空所 { 前のインスタンスを作成します。 // キャプチャ if (inCapturePhase) { (let i = dispatchListeners.length - 1; i >= 0; i--) { const {インスタンス、現在のターゲット、リスナー} = dispatchListeners[i]; if (インスタンス !== previousInstance && event.isPropagationStopped()) { 戻る; } executeDispatch(イベント、リスナー、現在のターゲット); 前のインスタンス = インスタンス; } } それ以外 { // バブル for (let i = 0; i < dispatchListeners.length; i++) { const {インスタンス、現在のターゲット、リスナー} = dispatchListeners[i]; if (インスタンス !== previousInstance && event.isPropagationStopped()) { 戻る; } executeDispatch(イベント、リスナー、現在のターゲット); 前のインスタンス = インスタンス; } } } processDispatchQueueItemsInOrder 関数は、判断に応じて前方および後方に移動することで、バブリングとキャプチャをシミュレートします。 ディスパッチ実行リスナーは executeDispatch 関数内で実行されます。 関数executeDispatch( イベント: ReactSyntheticEvent、 リスナー: 関数、 現在のターゲット: イベントターゲット、 ): 空所 { const type = event.type || '不明なイベント'; イベントの現在のターゲット = 現在のターゲット; リスナー(イベント); イベント.currentTarget = null; } 結論この記事は、イベントメカニズムの実行を明らかにすることを目的としています。関数実行スタックに従ってコードロジックを単純にリストします。コードを比較しないと理解するのは困難です。原理は最初に説明します。 React のイベントの仕組みはわかりにくく複雑です。さまざまな状況に基づいて多くの判断を行い、優先度関連のコードや合成イベントもあります。ここではそれらを一つ一つ説明していません。もちろん、まだ読んでいないからです。 私は通常、React を使用して簡単なモバイル ページを作成します。上司は読み込み速度が十分でないと文句を言っていましたが、私にできることは何もありませんでした。私の仕事に関する限り、Cocurrent があるかどうかは問題ではありませんでした。合成イベントはより複雑で、まったく不要でした。ただし、React の作者は非常に創造的です。ソース コードを読んでいなければ、一連のイベント メカニズムをシミュレートしているとは思いもしませんでした。 小さな思い
これまでこれらの疑問について考えたことはなかったのですが、今日ソースコードを読んだ後に考えてみました。
<div ネイティブ onClick={(e)=>{e.stopPropagation()}}> <div onClick={()=>{console.log("合成イベント")}}>合成イベント</div> </div> たとえば、この例では、ネイティブの onClick が送信をブロックした後、コンソールは「合成イベント」という 4 つの単語さえ入力しません。 以上がReactイベントメカニズムソースコード解析の詳細な内容です。Reactイベントメカニズムソースコードの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: mysql5.7.19 winx64 インストールおよび構成方法のグラフィック チュートリアル (win10)
ダイナミックレム1. まず、現在の長さの単位を紹介しましょうpx em Mの幅 / 漢字の幅 1em...
MySQL proxies_priv(シミュレートされたロール)を使用して同様のユーザーグループ管理...
# Windows および Linux 上の Redis のインストール デーモン構成Redis の...
<br />この記事は主に、初心者にXHTMLの基本知識と、XHTMLとHTMLの違いを...
導入Xiao A がコードを書いていたところ、DBA Xiao B が突然、「急いでユーザー固有情報...
目次負荷分散に nginx を使用するための 2 つのモジュール:アップストリームはロードノードプー...
NginxのGeoモジュールの紹介geo ディレクティブは、ngx_http_geo_module ...
マシンに初めて MySQL をインストールします。オペレーティングシステムはwin7ですmysqlの...
HTML 中心のフロントエンド開発は、ほぼ Web 標準の意味です。共通しているのは「分離」という考...
この記事の例は MySQL 5.0 以降で実行されます。ユーザー権限を付与するための MySQL コ...
背景webpackのバージョンを確認したいのですが、webpack -vを実行するとエラーが報告され...
サーバーの負荷を軽減するために、ユーザーが入力するときにフロントエンドページで簡単な検証を実行する必...
序文開発プロセスにおいて、変数の定義は非常に頻繁かつ基本的なタスクです。変数の使用シナリオと範囲に応...
この記事では、価格カレンダー効果を実現するためのVueの具体的なコードを例として紹介します。具体的な...
1. ユーザーにルーチン作成権限がある場合は、プロシージャ | 関数を作成できます。 2. ユーザー...