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 つのマスターと複数のスレーブのアーキテクチャ実装
Nginx をコンパイルしてインストールするときに、http_ssl_module などの一部のモジ...
まず、イメージをプルします(またはコンテナを作成するだけで、自然にプルされます)。 docker p...
最近、Vue プロジェクトについて知り、ElementUI でデータを xlsx および Excel...
HTML コード内の連続するスペースまたは空白行 (改行) はすべて 1 つのスペースとして表示され...
Linux で MySQL データベースをアンインストールするにはどうすればいいですか? 以下では、...
インストールREADMEに従ってインストールしてくださいドキュメントには、exa は Rust で実...
以前は、スクリプトは HTML 内のどこにでも配置できると思っていましたが、今日、要件に取り組んでい...
目次序文1. scp2をインストールする2. テスト/本番環境サーバーのSSHリモートログインアカウ...
目次1. カスタム指示1. グローバルカスタム指示を登録する2. グローバルカスタム指示を使用する3...
Tencent QQのホームページがリニューアルされ、Webフロントエンド開発がますます注目を集めて...
1. はじめにMySQL にログインすると、次のような警告が表示されることがよくあります。警告: コ...
ルートディレクトリとインデックスファイルroot ディレクティブは、ファイルの検索に使用するルート ...
この記事では、CentOS 7 に Chrome ブラウザをインストールする方法を紹介します。詳細は...
ルーティングvue-router4 では API の大部分は変更されていないため、変更点のみに焦点を...
1. Docker Composeの概要Compose は、マルチコンテナ Docker アプリケー...