ReactとはReact は、効率的で高速なユーザー インターフェイスを構築するためのシンプルな JavaScript UI ライブラリです。軽量なライブラリなので人気があります。コンポーネント設計パターン、宣言型プログラミングパラダイム、関数型プログラミングの概念に従って、フロントエンドアプリケーションをより効率的にします。仮想 DOM を使用して DOM を効率的に操作します。これは、高位コンポーネントから低位コンポーネントへの一方向のデータ フローに従います。 序文📝👉 私たちは、JavaScript で高速かつ応答性に優れた大規模な Web アプリケーションを構築するには、React が最適な方法だと考えています。 Facebook や Instagram でのパフォーマンスは良好です。公式ウェブサイトアドレス React のコンセプトは、大規模なプロジェクトに Reactは独自の基礎設計によりこの問題を回避しているため、React16.8のリリースではフロントエンド分野で3つのことだけを行います。高速応答、高速応答、またはTMD高速応答です。この記事ではHTMLから始めて、Reactのファイバーコンセプトに従い、非常に基本的なReactを模倣します。 まずは準備作業🤖html ページ全体をサポートし、React の動作をサポートする HTML が必要です。ページに <!DOCTYPE html> <html lang="ja"> <ヘッド> <メタ文字セット="UTF-8"> <meta name="viewport" content="width=デバイス幅、初期スケール=1.0"> <title>ドキュメント</title> </head> <本文> <div id="ルート"></div> <script type="module" src="./index.js" ></script> </本文> </html> コードのデバッグに役立ち、次の操作にも使用される JavaScript 基本的な操作を実装するために、次の反応を模倣し、イベントを ... 関数App() { 戻る ( <div> <input onInput={updateValue} 値={value} /> <h2>こんにちは {value}</h2> <時間 /> </div> ); } ... ReactがBabelでコンパイルされると、 ... React.createElement() は、 "div", ヌル、 React.createElement("入力", { onInput: 更新値、 値: 値、 })、 React.createElement("h2", null, "Hello", 値), React.createElement("hr"、null) は、 ); ... オンラインアドレス 変換されたコードから、React.createElement が複数のパラメータをサポートしていることがわかります。
{ タイプ: 'ノードラベル', 小道具:{ props:'イベント、クラスなど、ノードのプロパティ', children:'ノードの子ノード' } } ここでは、次の要件を満たす関数を書くことができます。
/** * 仮想DOM構造を作成する * @param {type} タグ名 * @param {props} プロパティオブジェクト * @param {children} 子ノード * @return {element} 仮想DOM */ const createElement = (type, props, ...children) => ({ タイプ、 小道具: { ...小道具、 子: children.map(child => 子の型 === "オブジェクト" ? 子供 : { タイプ: "TEXT_ELEMENT", 小道具: { ノード値: 子、 子供たち: []、 }, } )、 }, }); React ソースコードによる createElement の実装
「./mini/index.js」から {createElement,render} をインポートします。 定数 updateValue = e => executeRender(e.target.value); const executeRender = (値 = "ワールド") => { 定数要素 = createElement( "div", ヌル、 要素を作成します("入力", { onInput: 更新値、 値: 値、 })、 createElement("h2", null, "Hello", 値), 要素を作成します("hr"、null) ); レンダリング(要素、document.getElementById("root")); }; レンダリングを実行します。 レンダリング時に何が行われますか?以前のバージョン
React 16.8 より前では、レンダリングは次の手順で行われていました。
仮想DOMを取得し、上記の3つのステップを再帰的に実行します。レンダリングされたページは次のプロセスに似ています。 /** * 実際のDOMに仮想DOMを追加する * @param {要素} 仮想DOM * @param {container} 実際のDOM */ const render = (要素、コンテナ) => { 支配させる; /* 処理ノード(テキストノードを含む) */ if (要素の型 !== "オブジェクト") { DOM = document.createTextNode(要素); } それ以外 { DOM 要素を作成します。 } /* 処理プロパティ(イベントプロパティを含む) */ if (要素.props) { オブジェクト.キー(要素.プロパティ) .filter((キー) => キー != "子供") .forEach((アイテム) => { dom[item] = 要素.props[item]; }); オブジェクト.キー(要素.プロパティ) .filter((キー) => key.startsWith("on")) .forEach((名前) => { 定数eventType = name.toLowerCase().substring(2); dom.addEventListener(イベントタイプ、nextProps[名前]); }); } もし ( 要素.props && 要素.props.children && 要素.props.children.length ){ /* DOMに追加するためのループ */ element.props.children.forEach((child) => render(child, dom)); } コンテナに子要素を追加します。 }; 後バージョン(ファイバー) 上記のコードを書き終えると、この再帰呼び出しに問題があることがわかります。 上記の作業部分は、React では正式には renderer と呼ばれています。Renderer は、サードパーティが独自に実装できるモジュールです。また、reconsiler と呼ばれるコアモジュールもあります。reconsiler の主な機能の 1 つは、diff アルゴリズムです。どのページノードを更新する必要があるかを計算し、更新する必要があるノードの仮想 DOM を renderer に渡します。renderer はこれらのノードをページにレンダリングする役割を担っていますが、同期的です。レンダリングが開始されると、すべてのノードとその子ノードがレンダリングされ、完了するまでプロセスは終了しません。 React の公式プレゼンテーションには、この同期計算によって生じる遅延を明確に示す例があります。 DOM ツリーが大きい場合、JS スレッドの実行時間は比較的長くなる可能性があります。この間、JS スレッドと GUI スレッドは相互に排他的であるため、ブラウザは他のイベントに応答しません。JS の実行中はページが応答しません。この時間が長すぎると、ユーザーに遅延が発生する可能性があります。 この問題は 2 つのステップで解決できます。
ソリューションIは新しいAPIを導入します requestIdleCallback は、ブラウザがアイドル状態のときに呼び出されるコールバックを受け取ります。各呼び出しは、現在のアイドル時間を取得するために IdleDeadline を渡します。options は最大待機時間を渡すことができます。ブラウザがその時間までにアイドル状態でない場合は、強制的に実行されます。 window.requestIdleCallback は、ブラウザのアイドル期間中に呼び出される関数をキューに追加します。これにより、開発者は遅延されたキー イベントに影響を与えることなく、メイン イベント ループでバックグラウンドおよび低優先度の作業を実行できます。 ただし、この API はまだ実験段階であり互換性が低いため、React は独自のセットを正式に実装しました。この記事ではタスクのスケジュールにrequestIdleCallbackを引き続き使用します。 // 次の作業単位 let nextUnitOfWork = null /** * workLoop 作業ループ関数* @param {deadline} 期限*/ 関数 workLoop(期限) { // 作業ループ関数を停止する必要がありますか? let shouldYield = false // 次の作業単位があり、より優先度の高い作業がない場合は、while (nextUnitOfWork && !shouldYield) { をループします。 次の作業単位 = 実行作業単位( 次の作業単位 ) // 期限が近づいている場合は、作業ループを停止します。関数 shouldYield = deadline.timeRemaining() < 1 } // アイドル時間中に workLoop を実行する必要があることをブラウザに通知します リクエストアイドルコールバック(ワークループ) } // アイドル時間中に workLoop を実行する必要があることをブラウザに通知します リクエストアイドルコールバック(ワークループ) // ユニットイベントを実行し、次のユニットイベントを返す function performUnitOfWork(nextUnitOfWork) { //やるべきこと } ソリューションII ファイバーデータ構造を作成する Fiber の以前のデータ構造はツリーであり、親ノードの子ノードが子ノードを指しますが、このポインタだけでは中断と継続を実現できません。たとえば、現在親ノード A があり、A には 3 つの子ノード B、C、D があります。C に移動すると、中断されます。もう一度開始すると、C の下でどれを実行すればよいかわかりません。これは、C しか知らず、その親ノードへのポインターも、兄弟へのポインターもないためです。 Fiber はこのような構造を変換し、親ノードと兄弟ノードへのポインタを追加します。
各ファイバーには、最初の子、次の兄弟、および親へのリンクがあります。このデータ構造により、次の作業単位をより簡単に見つけることができます。A
このデータ構造を使用してファイバーを実装します //初期ルートファイバーを作成する wipRoot = { dom: コンテナ、 プロパティ: { 子: [要素] }, }; 作業単位を実行します(wipRoot); 次に、 /** * performUnitOfWork はタスクを実行するために使用されます * @param {fiber} 現在のファイバータスク * @return {fiber} 次のファイバータスク */ const performUnitOfWork = ファイバー => { if (!fiber.dom) fiber.dom = createDom(fiber); // マウントするDOMを作成する const elements = fiber.props.children; // 現在の要素の下にあるすべての兄弟ノード // 親ノードがある場合は、現在のノードを親ノードにマウントする if (fiber.return) { fiber.return.dom.appendChild(fiber.dom); } prevSibling を null にします。 /* コードの後半で、ここでロジックを抽出します*/ if (要素 && 要素の長さ) { elements.forEach((要素, インデックス) => { 定数newFiber = { タイプ: 要素.type、 プロパティ: 要素.props、 戻り値: ファイバー、 dom: null、 }; // 親の子は最初の子要素を指します if (index === 0) { ファイバーの子 = newFiber; } それ以外 { // 各子要素には次の子要素へのポインターがあります prevSibling.sibling = newFiber; } 前兄弟 = 繊維; }); } // 最初に子要素を検索し、子要素がない場合は兄弟要素を検索します // 兄弟要素がない場合は親要素に戻ります // 最後にルートノードで終了します // トラバーサルの順序は上から下、左から右です if (fiber.child) { fiber.child を返します。 } それ以外 { nextFiber = fiber;とします。 (nextFiber) の間 { if (nextFiber.sibling) { nextFiber.sibling を返します。 } nextFiber = nextFiber.return; } } } バージョン後(調整)現在のルート 調整は、実際には仮想 DOM ツリーの差分操作です。更新前と更新後のファイバー ツリーを比較します。比較結果を取得した後、変更されたファイバーに対応する DOM ノードのみが更新されます。
ルート ノードが更新される前にファイバー ツリーを保存するための currentRoot 変数を追加し、ファイバーが更新される前にファイバー ツリーを保存するための alternate 属性をファイバーに追加しました。 currentRoot = null とする 関数レンダリング(要素、コンテナ){ wipRoot = { // 代替を省略: currentRoot } } 関数commitRoot(){ commitWork(wipRoot.child) /* ファイバーツリーの方向を変更し、キャッシュ内のファイバーツリーをページ内のファイバーツリーに置き換えます。 */ 現在のルート = wipRoot wipRoot = null }
和解する子供達
ファイバーツリーを比較すると
/** * 調整子ノード * @param {fiber} ファイバー * @param {elements} ファイバーの子ノード */ 関数 reconcileChildren(wipFiber, elements) { let index = 0; // 子ノードのインデックス値をカウントするために使用されます let oldFiber = wipFiber.alternate && wipFiber.alternate.child; // 更新時にのみ生成されます let prevSibling; // 前の兄弟ノード while (index < elements.length || oldFiber) { /** * 子ノードをトラバースします* oldFiber は、更新トリガーか最初のトリガーかを判断します。更新トリガーの場合は、要素の下のすべてのノードが対象となります*/ 新しいファイバーを作成します。 const 要素 = 要素[インデックス]; const sameType = oldFiber && element && element.type == oldFiber.type; // ファイバータイプが同じかどうか/** * 更新時* 属性が異なる同じタグの場合は、属性を更新します*/ if (同じタイプ) { 新しいファイバー = { タイプ: oldFiber.type、 props: element.props, //プロパティのみ更新 dom: oldFiber.dom, 親: wipFiber、 代替: oldFiber、 効果タグ: "UPDATE", }; } /** * 異なるタグ、つまりタグを置き換えるか、新しいタグを作成します*/ if (要素 && !sameType) { 新しいファイバー = { タイプ: 要素.type、 プロパティ: 要素.props、 dom: null、 親: wipFiber、 代替: null、 効果タグ: "PLACEMENT", }; } /** * ノードが削除されました */ if (oldFiber && !sameType) { oldFiber.effectTag = "削除"; 削除.push(oldFiber); } oldFiber の場合、 oldFiber = oldFiber.sibling; // 親の子は最初の子要素を指します if (index === 0) { // ファイバーの最初の子はその子です wipFiber.child = newFiber; } それ以外 { // fiber の他の子ノードは、最初の子ノードの兄弟ノードです。prevSibling.sibling = newFiber; } // 新しく作成された newFiber を prevSibling に割り当てて、newFiber の兄弟ノードを追加しやすくします。prevSibling = newFiber; // インデックス値 + 1 インデックス++; } } コミット時に、ファイバーノードの バージョン後(コミット) commitWork では、ファイバーの effectTag が実際の DOM 操作を処理すると判断されます。
/** * @param {fiber} ファイバー構造の仮想DOM */ 関数 commitWork(ファイバー) { if (!fiber) 戻り値: DOM 親クラスを fiber.parent.dom に変更します。 if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) { domParent.appendChild(fiber.dom); } そうでない場合 (fiber.effectTag === "UPDATE" && fiber.dom != null) { fiber.dom を更新します。 } そうでない場合 (fiber.effectTag === "削除") { domParent の子を削除します。 } // 子要素と兄弟要素を再帰的に操作する commitWork(fiber.child); コミットワーク(fiber.sibling); } ここで、 /* isEvent: イベント属性を取得します。isProperty: ノード以外の、イベント以外の属性を取得します。isNew: 前後で変更された属性を取得します*/ const isEvent = key => key.startsWith("on"); const isProperty = key => key !== "children" && !isEvent(key); const isNew = (prev, next) => key => prev[key] !== next[key]; /** * DOMプロパティを更新 * @param {dom} ファイバーDOM * @param {prevProps} ファイバー DOM の古いプロパティ * @param {nextProps} ファイバー DOM の新しいプロパティ */ 関数 updateDom(dom, prevProps, nextProps) { /** * 便利な古い属性* 1. on で始まるイベント属性を取得します* 2. 削除されたイベントを取得します* 3. 削除されたイベントの監視をキャンセルします*/ オブジェクト.keys(前のプロパティ) .filter(イベント) .filter(key => !(nextProps 内のキー)) .forEach(名前 => { 定数eventType = name.toLowerCase().substring(2); dom.removeEventListener(eventType、prevProps[名前]); }); /** * 便利な古い属性* 1. 非イベント属性と非子ノード属性を取得します* 2. 削除された属性を取得します* 3. 属性を削除します*/ オブジェクト.keys(前のプロパティ) .filter(プロパティ) .filter(key => !(nextProps 内のキー)) .forEach(key => dom[key]を削除します); /** * 便利な新しい属性 * 1. 非イベント属性と非子ノード属性を取得します * 2. 前後に変更された属性を取得します * 3. 属性を追加します */ オブジェクト.keys(次のプロパティ) .filter(プロパティ) .filter(isNew(前のプロパティ、次のプロパティ)) .forEach(名前 => { dom[名前] = nextProps[名前]; }); /** * 便利な新しい属性 * 1. on で始まるイベント属性を取得します * 2. 前後に変更されたイベント属性を取得します * 3. 新しく追加されたイベント属性のリスナーを追加します */ オブジェクト.keys(次のプロパティ) .filter(イベント) .filter(isNew(前のプロパティ、次のプロパティ)) .forEach(名前 => { 定数eventType = name.toLowerCase().substring(2); dom.addEventListener(イベントタイプ、nextProps[名前]); }); } DOM に対する一連の操作が完了したら、新しく変更された DOM をページにレンダリングします。input イベントが実行されると、ページは再度レンダリングされますが、このときにファイバー ツリーを更新するロジックに入ります。 ミッション完了! 完全なコードは私の github にあります。 結論と要約💢 結論は
要約する
ここまで、この記事を読んでくださりありがとうございます。この記事がお役に立てば幸いです。ご質問があれば、遠慮なくご指摘ください。仕事の都合で、この記事を約1か月間断続的に書いてきました。 👋: github へジャンプします。スターを付けていただけると嬉しいです。皆さんありがとうございます。 参考文献 🍑: 手書きシリーズ - プラチナレベルのReactを実現する 🍑: 自分で React を構築する (強く推奨) 🍑: React の Fiber アーキテクチャを手書きし、その原理を深く理解する 🍑: シンプルなReactを手書きする 🍑: 妙威教室のDasheng先生がReactのファイバーとフックのアーキテクチャを書きました 🍑: React Fiber アーキテクチャ 🍑: シンプルなReactを手書きする これで、HTML から React を実装する方法を説明するこの記事は終わりです。より関連性の高い HTML 実装 React コンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後も 123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
>>: MySQL クラスタの詳細な説明: 1 つのマスターと複数のスレーブのアーキテクチャ実装
目次序文1. gzip圧縮を設定する2. 詳細設定3. nginxサービスを再起動する要約する序文ウ...
カーネル: [root@opop ~]# cat /etc/centos-release CentO...
マウス効果では、setTimeout を使用して固定時間にノードを生成し、ノードを削除し、生成された...
正直に言うと、この質問には MySQL のコア知識がかなり必要で、コンピュータ ネットワークの知識を...
この記事では、Ubuntu 環境で xdebug をコンパイルしてインストールする方法について説明し...
1. 仮想マシンに共有フォルダを設定します。 1. 処理する仮想マシンを選択し、右クリックして設定...
1. テーブル内のフィールドの種類を表示する テーブル名を記述する desc テーブル名 2. テー...
大きなことも小さなことも考えて、方向転換しましょう。 Linux では非常に大きなファイルに遭遇する...
目次1. コンポーネントの登録1.1 グローバル登録1.2 グローバルコンポーネントの登録プロセス1...
デフォルトでは、Linux の MySQL はテーブル名の大文字と小文字を区別します。 MySQL ...
目次dnsmasq をインストールして設定するChinaDNS をインストールして設定するshado...
MySQL ストレージ エンジンの概要ストレージ エンジンとは何ですか? MySQL のデータは、さ...
まず、setIntervalはフックとしてカプセル化されます👇 'react' から...
1. 何ですか視差スクロールとは、複数の背景レイヤーを異なる速度で動かすことで、3次元のモーション...
目次.vue ファイルの解析文書情報を抽出するコンポーネント名、説明、プロパティ、メソッド、モデルを...