この記事はPReact10.5.13のソースコードを理解するのに役立ちます

この記事はPReact10.5.13のソースコードを理解するのに役立ちます

Reactのソースコードは何度か読んでいますが、毎回忠実に読んでいるわけではありません。PReactの部分だけを学習しました。ソースコードの説明はネット上にたくさんありますが、基本的に古いものばかりなので、自分で整理していきます。

render.js 部分

'./constants' から { EMPTY_OBJ, EMPTY_ARR } をインポートします。
'./diff/index' から commitRoot、diff をインポートします。
'./create-element' から createElement, Fragment をインポートします。
'./options' からオプションをインポートします。

/**
 * Preact仮想ノードをDOM要素にレンダリングする
 * @param {import('./internal').ComponentChild} vnode レンダリングする仮想ノード
 * @param {import('./internal').PreactElement} parentDom インポートするDOM要素
 * レンダリングする
 * @param {import('./internal').PreactElement | object} [replaceNode] オプション: 再利用を試みる
 * `replaceNode` をルートとする既存の DOM ツリー
 */
エクスポート関数render(vnode, parentDom, replaceNode) {
 if (options._root) options._root(vnode, parentDom);

 // `hydrate()` の `replaceNode` パラメータを悪用して、
 // DOMの代わりに`hydrate`関数を渡すことでハイドレーションモードかどうかを指定します
 // 要素..
 isHydrating = typeof replaceNode === 'function' とします。

 // 同じページに対して `render()` を複数回呼び出すことをサポートできるようにするには
 // DOMノードを取得するには、前のツリーへの参照を取得する必要があります。
 // これはDOMノードに新しい`_children`プロパティを割り当てることで実現されます。
 // 最後にレンダリングされたツリーに追加します。デフォルトではこのプロパティは存在しないため、
 // は、初めて新しいツリーをマウントすることを意味します。
  // DOM ノード上のレンダリング関数への複数の呼び出しをサポートするには、DOM ノードに drinking を追加して、最後のレンダリングを指す仮想 DOM ツリーを取得する必要があります。
  // このプロパティはデフォルトで null を指しています。つまり、初めて新しいツリーを装備することになります // したがって、最初は oldVNode は null です (isHydrating の値に関係なく)。ただし、このノードで render を繰り返し呼び出すと、oldVNode に値が割り当てられます let oldVNode = isHydrating
  ? ヌル
  : (replaceNode && replaceNode._children) || parentDom._children;

 // vnode を Fragment でラップし、replaceNode に vnode を割り当て、parentDom の _children = (
  (!isHydrating && replaceNode) ||
  親ドメイン
 )._children = createElement(フラグメント、null、[vnode]);

 // 比較後に呼び出す必要があるエフェクトのリスト。
  // cdm や cdu など、diff 後にさまざまなライフサイクル処理を実行する必要があるコンポーネントを配置するために使用します。componentWillUnmount は diffChildren の unmount 関数内で実行され、commitRoot が使用されている場合は実行されません。let commitQueue = [];
 差分(
  parentDom, // これは、parentDomの_childrenプロパティを使用して[vnode]を指します。 // 新しいvnodeツリーを決定し、DOM要素に格納します。
  // カスタム `_children` プロパティ。
  vノード、
  oldVNode || EMPTY_OBJ, // 古いツリー EMPTY_OBJ,
  parentDom.ownerSVGElement !== 未定義、
    // extraDomChildren、このパラメータはDOMの再利用に使用されます! isHydrating && replaceNode
   ? [ノードを置換]
   : 古いVNode
   ? ヌル
   : parentDom.firstChild // parentDom に子ノードがある場合、子ノード全体が再利用されるノードとして使用されますか? EMPTY_ARR.slice.call(parentDom.childNodes)
   : ヌル、
  コミットキュー、
    // oldDom、後続のメソッドで挿入位置をマークするために使用されます!isHydrating && replaceNode
   ? ノードを置換
   : 古いVNode
   ? 古いVNode._dom
   : 親Dom.firstChild、
  水分補給
 );

 // キューに入れられたエフェクトをすべてフラッシュする
  // commitQueue 内のすべての nodes_renderCallbacks でメソッド commitRoot(commitQueue, vnode) を呼び出します。
}

/**
 * Preact仮想ノードのデータを使用して既存のDOM要素を更新する
 * @param {import('./internal').ComponentChild} vnode レンダリングする仮想ノード
 * @param {import('./internal').PreactElement} parentDom インポートするDOM要素
 * アップデート
 */
エクスポート関数hydrate(vnode, parentDom) {
 レンダリング(vnode、parentDom、hydrate);
}

create-context.js 部分

コンテキストの使用:

プロバイダのpropsに値属性がある

Consumerで直接価値を得る

'preact' から {createContext、h、render} をインポートします。

定数FontContext = createContext(20);

関数Child() {
 <FontContext.Consumer> を返す
 {fontSize=><div style={{fontSize:fontSize}}>子</div>}
 </FontContext.Consumer>
}
関数App(){
 <Child/> を返す
}
与える(
 <FontContext.Provider 値 = {26}>
 <アプリ/>
 </FontContext.Provider>,
 ドキュメント.getElementById('アプリ')
);

ソースコードを見てみましょう:

'./component' から enqueueRender をインポートします。

エクスポートlet i = 0;

エクスポート関数createContext(defaultValue, contextId) {
 contextId = '__cC' + i++; // 一意のIDを生成する

 定数コンテキスト = {
  _id: コンテキストID、
  _defaultValue: デフォルト値、
  /** @type {import('./internal').FunctionComponent} */
  コンシューマー(props, contextValue) {
   // props.children( を返す
   // context[contextId] ? context[contextId].props.value : defaultValue
   // );
   props.children(contextValue) を返します。
  },
  /** @type {import('./internal').FunctionComponent} */
  プロバイダー(props) {
   if (!this.getChildContext) { // 初めて呼び出されたときにいくつかの初期化操作を実行します let subs = [];
    ctx = {} とします。
    ctx[コンテキストID] = this;
       
       // diff 操作では、コンポーネントが Consumer 内にあると判断された場合、sub が呼び出されて subscribe されます。
       // 同時に、このノードの後続のすべての差分はこのコンテキストを保持し、サブメソッドを呼び出して // コンテキストには階層的な優先順位があり、コンポーネントは最も近いコンテキストに最初に追加されます this.getChildContext = () => ctx; 

    this.shouldComponentUpdate = function(_props) {
     if (this.props.value !== _props.value) {
      // ここでの強制的な値の伝播は、`options.debounceRendering` がバイパスされている場合にのみ必要だったと思います:
      // https://github.com/preactjs/preact/commit/4d339fb803bea09e9f198abf38ca1bf8ea4b7771#diff-54682ce380935a717e41b8bfc54737f6R358
      // ただし、これらのケースでは、値を修正しても、すべてのノードが二重にレンダリングされます。
      // 強制同期モードを使用しないように伝える方がよいかもしれません。
      // 現在、クラス コンポーネントで `useContext()` を使用すると、その `this.context` 値が上書きされます。
      // subs.some(c => {
      // c.context = _props.value;
      // レンダリングをキューに追加します(c);
      // });

      // subs.some(c => {
      // c.context[contextId] = _props.value;
      // レンダリングをキューに追加します(c);
      // });
            // enqueueRender は最終的に renderComponent 関数に入り、diff、commitRoot、updateParentDomPointers などの操作を実行します。subs.some(enqueueRender);
     }
    };

    this.sub = c => {
     subs.push(c);// サブスクリプション配列を入力します。
     古いものを c.componentWillUnmount にします。
     c.componentWillUnmount = () => { // componentWillUnmountをオーバーライドする
      subs.splice(subs.indexOf(c), 1);
      (古い) の場合、old.call(c);
     };
    };
   }

   props.children を返します。
  }
 };

 // Devtoolsはコンテキストオブジェクトにアクセスする必要がある。
 // プロバイダに遭遇します。これはサポートするために必要です
 // 代わりにコンテキストオブジェクトに `displayName` を設定します
 // コンポーネント自体に。参照:
 // https://reactjs.org/docs/context.html#コンテキスト表示名
 // createContext は最終的に、Provider と Consumer の 2 つの関数を持つコンテキスト オブジェクトを返します。// 同時に、Consumer 関数の contextType プロパティと Provider 関数の _contextRef プロパティは両方ともコンテキストを指します。
 戻り値 (context.Provider._contextRef = context.Consumer.contextType = context);
}

したがって、プロバイダー コンポーネントの場合、レンダリング時に getChildContext メソッドがあるかどうかが判断されます。ある場合は、globalContext を取得して渡します。

c.getChildContext が null の場合
    グローバルコンテキスト = 割り当て(割り当て({}、グローバルコンテキスト)、c.getChildContext());
   }

   if (!isNew && c.getSnapshotBeforeUpdate != null) {
    スナップショット = c.getSnapshotBeforeUpdate(oldProps, oldState);
   }

   isTopLevelFragment = とします
    tmp != null && tmp.type === Fragment && tmp.key == null;
   renderResult を isTopLevelFragment とします。tmp.props.children: tmp;

   diffChildren(
    親ドメイン、
    Array.isArray(renderResult) ? renderResult : [renderResult],
    新しいVNode、
    古いVNode、
    グローバルコンテキスト、
    、
    過剰DomChildren、
    コミットキュー、
    オールドドム、
    水分補給
   );

レンダリングが Consumer、つまり contextType 属性に遭遇すると、まず Context からプロバイダーを取得し、次にコンポーネントが取得するコンテキスト情報としてプロバイダーの props の値を取得します。同時に、プロバイダーのサブメソッドが呼び出され、サブスクライブします。プロバイダーの shouldComponentUpdate で値が変更されると、すべてのサブスクライバーが enqueueRender 関数に入ります。

したがって、ソース コードでは、globalContext オブジェクトの各キーは Context.Provider を指します。componentContext は、コンポーネントが配置されている Consumer によって渡されたコンテキスト情報、つまり、ペアになっている Provider の props の値を表します。

同時に、Provider の shouldComponentUpdate メソッドは ·this.props.value !== _props.value· を使用します。では、this.props はどこから来るのでしょうか?プロバイダーには関連するプロパティがありません。

重要なのは次の場所です。renderメソッドがないと判断された場合、Compoentを使用してオブジェクトをインスタンス化し、renderメソッドをdoRenderに設定し、コンストラクタはnewType(現在の関数)を指し、doRenderでthis.constructorメソッドが呼び出されます。

// 新しいコンポーネントをインスタンス化する
    newType に 'prototype' が含まれる場合、newType.prototype.render が返されます。
     // @ts-ignore 上記のチェックはnewTypeが構築されることを検証します
     newVNode._component = c = new newType(newProps, componentContext); // eslint-disable-line new-cap
    } それ以外 {
     // @ts-ignore 信じてください、コンポーネントは私たちが望むインターフェースを実装します
     newVNode._component = c = 新しいコンポーネント(newProps、componentContext);
     c.コンストラクタ = newType;
     c.render = doRender;
    }
/** PFC バッキング インスタンスの `.render()` メソッド。 */
関数doRender(props, state, context) {
 this.constructor(props, context) を返します。
}

差分部分

差分部分はより複雑なので、全体像を整理しました

Blog Garden のエディターには、特に Mac で使用する場合にバグが多すぎると本当に不満に思います。たとえば、2 回目のアップロードではコードを送信できず、割り当てと貼り付けは使用できません。 。 。

感情だけが私をここに更新させている

PReact10.5.13 ソースコードの理解を助けるこの記事はこれで終わりです。より関連性の高い PReact10.5.13 ソースコードの内容については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • Reactコンポーネントが子コンポーネントを強化する方法
  • React Hooksの使用例
  • React Native の基本原則の深い理解 (Bridge of React Native)
  • React+Koa によるファイルアップロードの実装例
  • Reactフックとzarmコンポーネントライブラリ構成に基づいてh5フォームページを開発するためのサンプルコード
  • React antd タブの切り替えによりサブコンポーネントが繰り返し更新される
  • ReactJs 基礎チュートリアル - 基本編
  • 反応ルーティングでパラメータを渡すいくつかの方法についての簡単な説明
  • React.Childrenの詳しい使い方

<<:  Windows での Apache+Tomcat7 負荷分散構成方法の詳細な説明

>>:  MacにMySQLをインストールするときに忘れたパスワードを変更する方法

推薦する

MySQL ビューの原理と基本操作例

この記事では、例を使用して、MySQL ビューの原理と基本操作を説明します。ご参考までに、詳細は以下...

dockerコンテナにvimをインストールするソリューション

目次物語の始まりvimをインストールし、hadoop-hive.envを編集します。不注意で回避しま...

Vueのフロントエンドとバックエンドのデータのやり取りと表示を理解する方法

目次1. 技術概要2. 技術的な詳細1. インターフェースからバックエンドデータを取得する2. フロ...

MySQLがbinlogファイルを手動で登録し、マスタースレーブ異常を引き起こす理由

1. 問題の原因友人の @水米田 から、POSITION に基づくマスタースレーブについて質問があり...

Vueプラグインの実装で発生した問題の概要

目次シーン紹介プラグインの実装問題1: 重複したヘッダーコンポーネント質問2: 別の実装アイデア質問...

Vueのprovideとinjectの使い方と原則を分析する

まず、provide/inject を使用する理由について説明しましょう。祖父コンポーネントと孫コン...

MySQL のユニークインデックスと通常のインデックスのどちらを選択すればよいでしょうか?

ユーザー テーブルを設計するときに、各人の ID 番号が一意であり、検索する必要があるシナリオを想像...

CentOS仮想マシンの時刻を変更する方法

上はシステム時間、下はハードウェア時間です。ここでは変更を加えているので、同じくらいの速さになってい...

Dockerコンテナの接続と通信の実装

ポート マッピングは、Docker を別のコンテナーに接続する唯一の方法ではありません。 Docke...

MySQL 8.0.18 のインストールと設定のグラフィックチュートリアル

学習目標: Windowsシステムを使用してMySQLデータベースをインストールする方法を学びます。...

イメージのアップロードとダウンロードに docker をプロキシするためのプライベート ライブラリとして nexus を使用する

1. Nexusの設定1. Dockerプロキシを作成する外部ネットワーク ウェアハウスからローカル...

Docker Alibaba Cloud RocketMQ 4.5.1 のデプロイプロセスの詳細な説明

検索ミラー docker 検索 rocketmq画像バージョンを表示他の画像を表示したい場合は、画像...

スクロールバーを非表示にしてコンテンツをスクロールする CSS サンプルコード

序文ページの HTML 構造にネストされたボックスが多数含まれている場合、ページに複数の垂直スクロー...

MySQLデータベーステーブルの容量を確認する方法の例

この記事では、MySQL のデータベース テーブルの容量を確認するためのコマンド ステートメントを紹...

XHTML 入門チュートリアル: テキストの書式設定と特殊文字

<br />このセクションでは、XHTML でテキストの書式設定と特殊文字を実装する方法...