目次
JS オブジェクトを使用した DOM ツリーのシミュレーション
添加新节点
移除老节点
节点的替换
比较子节点
完整的代码
总结
ホームページ ウェブフロントエンド jsチュートリアル 独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

Oct 29, 2020 pm 05:30 PM
javascript node.js フロントエンド

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

独自の仮想 DOM を構築するには、2 つのことを知っておく必要があります。 React やその他の仮想 DOM 実装のソース コードは非常に大きく複雑であるため、詳しく調べる必要さえありません。しかし、実際には、仮想 DOM の主要部分に必要なコードは 50 行未満です。

2 つの概念があります:

  • 仮想 DOM は実際の DOM のマッピングです
  • 仮想 DOM ツリー内の一部のノードが変更されると、 get 新しい仮想ツリーを取得します。このアルゴリズムは 2 つのツリー (新旧) を比較し、相違点を見つけて、実際の DOM に対応する変更を加えます。

JS オブジェクトを使用した DOM ツリーのシミュレーション

まず、何らかの方法で DOM ツリーをメモリに保存する必要があります。これは通常の JS オブジェクトを使用して実行できます。次のようなツリーがあるとします:

ログイン後にコピー
ログイン後にコピー
      
  • item 1
  •   
  • item 2

単純そうに見えますか?JS オブジェクトでそれを表現するにはどうすればよいですか?

{ type: ‘ul’, props: { ‘class’: ‘list’ }, children: [
  { type: ‘li’, props: {}, children: [‘item 1’] },
  { type: ‘li’, props: {}, children: [‘item 2’] }
] }
ログイン後にコピー

ここで注意すべき点が 2 つあります:

  • DOM 要素を表すには次のオブジェクトを使用します
{ type: ‘…’, props: { … }, children: [ … ] }
ログイン後にコピー
ログイン後にコピー
  • DOM テキスト ノードを表すには通常の JS 文字列を使用します

ただし、表現されるコンテンツはたくさんありますこのように、ドムツリーは非常に難しいです。理解を容易にするために、ここに補助関数を作成しましょう:

function h(type, props, …children) {
  return { type, props, children };
}
ログイン後にコピー

このメソッドを使用して、最初のコードを再配置します:

h(‘ul’, { ‘class’: ‘list’ },
  h(‘li’, {}, ‘item 1’),
  h(‘li’, {}, ‘item 2’),
);
ログイン後にコピー

これは非常に単純に見えますが、さらに進めることができます。ここでは、次のように JSX が使用されています:

ログイン後にコピー
ログイン後にコピー
      
  • item 1
  •   
  • item 2

は次のようにコンパイルされます:

React.createElement(‘ul’, { className: ‘list’ },
  React.createElement(‘li’, {}, ‘item 1’),
  React.createElement(‘li’, {}, ‘item 2’),
);
ログイン後にコピー

これに見覚えがあるでしょうか? React.createElement(...) を先ほど定義した h(...) 関数に置き換えることができれば、JSX 構文を使用することもできます。実際、ソース ファイルの先頭に次のコメントを追加するだけです:

/** @jsx h */
ログイン後にコピー
      
  • item 1
  •   
  • item 2

これは実際に Babel に「おい、弟よ、# を使用して JSX 構文をコンパイルするのを手伝ってくれ」と伝えます。 React.createElement(…) の代わりに ##h( ...) 関数を使用すると、Babel がコンパイルを開始します。 '

要約すると、DOM は次のように記述します:

/** @jsx h */
const a = (
  
ログイン後にコピー
        
  • item 1
  •     
  • item 2
  •   
);Babel は、DOM を次のようなコードにコンパイルするのに役立ちます:

const a = (
  h(‘ul’, { className: ‘list’ },
    h(‘li’, {}, ‘item 1’),
    h(‘li’, {}, ‘item 2’),
  );
);
ログイン後にコピー
When the function

" h" 実行すると、通常の JS オブジェクト、つまり仮想 DOM が返されます。

const a = (
  { type: ‘ul’, props: { className: ‘list’ }, children: [
    { type: ‘li’, props: {}, children: [‘item 1’] },
    { type: ‘li’, props: {}, children: [‘item 2’] }
  ] }
);
ログイン後にコピー
仮想 DOM から実際の DOM へのマップ

OK、これで DOM が完成しました。ツリーでは、通常の JS オブジェクト表現と独自の構造を使用します。これは素晴らしいことですが、そこから実際の DOM を作成する必要があります。

まず、いくつかの仮定を立てて、いくつかの用語を宣言しましょう:

  • $ 」で始まる変数を使用して、実際の DOM ノード (要素、テキスト ノード) を表します。したがって、 $parent は実際の DOM 要素になります。
  • 仮想 DOM は、 React と同様に、
  • node
* という名前の変数を使用して表されます。 1 つのルート ノード - 他のすべてのノードはその中にあります

それでは、仮想 DOM ノードを取得し、実際の DOM ノードを返す関数

createElement(...) を作成しましょう。ここでは props 属性と children 属性を無視します:

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  return document.createElement(node.type);
}
ログイン後にコピー
上記の方法を使用すると、テキスト ノードと Dom 要素ノードという 2 種類のノードを作成することもできます。 JS オブジェクトの場合:

{ type: ‘…’, props: { … }, children: [ … ] }
ログイン後にコピー
ログイン後にコピー
したがって、関数

createElement で仮想テキスト ノードと仮想要素ノードを渡すことができます。これは実現可能です。

次に、子ノードについて考えてみましょう。それぞれはテキスト ノードまたは要素です。したがって、

createElement(…) 関数を使用して作成することもできます。はい、これは再帰のように機能するため、各要素の子に対して createElement(…) を呼び出してから、appendChild() を使用して要素に追加できます。

function createElement(node) {
  if (typeof node === ‘string’) {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}
ログイン後にコピー
うわー、良さそうですね。まず、ノードの

props プロパティを脇に置きます。後でまた話しましょう。仮想 DOM の基本概念は複雑になるため、理解する必要はありません。

完全なコードは次のとおりです:

/** @jsx h */

function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === 'string') {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

const a = (
  
ログイン後にコピー
        
  • item 1
  •     
  • item 2
  •   
); const $root = document.getElementById('root'); $root.appendChild(createElement(a));2 つの仮想 DOM ツリーの違いを比較します

これで、仮想 DOM を実際の DOM に変換できるようになります。これには、 2 つのツリー DOM ツリーの違い。基本的に、新しいツリーと古いツリーを比較するアルゴリズムが必要です。これにより、何が変更されたのかを把握し、それに応じて実際の DOM を変更できるようになります。

DOM ツリーを比較するにはどうすればよいですか?次の状況に対処する必要があります:

    新しいノードを追加するには、
  • appendChild(...) メソッドを使用してノードを追加します

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

    古いノードを削除します。
  • removeChild(...) メソッドを使用して古いノードを削除します。

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

    ノードの置換、
  • replaceChild(...) メソッドを使用します

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

ノードが同じ場合 -子ノードを詳細に比較する必要があります

独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

编写一个名为 updateElement(…) 的函数,它接受三个参数—— $parentnewNodeoldNode,其中 $parent 是虚拟节点的一个实际 DOM 元素的父元素。现在来看看如何处理上面描述的所有情况。

添加新节点

function updateElement($parent, newNode, oldNode) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  }
}
ログイン後にコピー

移除老节点

这里遇到了一个问题——如果在新虚拟树的当前位置没有节点——我们应该从实际的 DOM 中删除它—— 这要如何做呢?

如果我们已知父元素(通过参数传递),我们就能调用 $parent.removeChild(…) 方法把变化映射到真实的 DOM 上。但前提是我们得知道我们的节点在父元素上的索引,我们才能通过 $parent.childNodes[index] 得到该节点的引用。

好的,让我们假设这个索引将被传递给 updateElement 函数(它确实会被传递——稍后将看到)。代码如下:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  }
}
ログイン後にコピー

节点的替换

首先,需要编写一个函数来比较两个节点(旧节点和新节点),并告诉节点是否真的发生了变化。还有需要考虑这个节点可以是元素或是文本节点:

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === ‘string’ && node1 !== node2 ||
         node1.type !== node2.type
}
ログイン後にコピー

现在,当前的节点有了 index 属性,就可以很简单的用新节点替换它:

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  }
}
ログイン後にコピー

比较子节点

最后,但并非最不重要的是——我们应该遍历这两个节点的每一个子节点并比较它们——实际上为每个节点调用updateElement(…)方法,同样需要用到递归。

  • 当节点是 DOM 元素时我们才需要比较( 文本节点没有子节点 )
  • 我们需要传递当前的节点的引用作为父节点
  • 我们应该一个一个的比较所有的子节点,即使它是 undefined 也没有关系,我们的函数也会正确处理它。
  • 最后是 index,它是子数组中子节点的 index
function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i <h2 id="完整的代码">完整的代码</h2><p><strong>Babel+JSX</strong><br>/<em>* @jsx h </em>/</p><pre class="brush:php;toolbar:false">function h(type, props, ...children) {
  return { type, props, children };
}

function createElement(node) {
  if (typeof node === 'string') {
    return document.createTextNode(node);
  }
  const $el = document.createElement(node.type);
  node.children
    .map(createElement)
    .forEach($el.appendChild.bind($el));
  return $el;
}

function changed(node1, node2) {
  return typeof node1 !== typeof node2 ||
         typeof node1 === 'string' && node1 !== node2 ||
         node1.type !== node2.type
}

function updateElement($parent, newNode, oldNode, index = 0) {
  if (!oldNode) {
    $parent.appendChild(
      createElement(newNode)
    );
  } else if (!newNode) {
    $parent.removeChild(
      $parent.childNodes[index]
    );
  } else if (changed(newNode, oldNode)) {
    $parent.replaceChild(
      createElement(newNode),
      $parent.childNodes[index]
    );
  } else if (newNode.type) {
    const newLength = newNode.children.length;
    const oldLength = oldNode.children.length;
    for (let i = 0; i 
    
ログイン後にコピー
  • item 1
  •     
  • item 2
  •    ); const b = (   
          
    • item 1
    •     
    • hello!
    •   
    ); const $root = document.getElementById('root'); const $reload = document.getElementById('reload'); updateElement($root, a); $reload.addEventListener('click', () => {   updateElement($root, b, a); });

    HTML

    <button>RELOAD</button>
    <p></p>
    ログイン後にコピー

    CSS

    #root {
      border: 1px solid black;
      padding: 10px;
      margin: 30px 0 0 0;
    }
    ログイン後にコピー

    打开开发者工具,并观察当按下“Reload”按钮时应用的更改。

    独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介

    总结

    现在我们已经编写了虚拟 DOM 实现及了解它的工作原理。作者希望,在阅读了本文之后,对理解虚拟 DOM 如何工作的基本概念以及在幕后如何进行响应有一定的了解。

    然而,这里有一些东西没有突出显示(将在以后的文章中介绍它们):

    • 设置元素属性(props)并进行 diffing/updating
    • 处理事件——向元素中添加事件监听
    • 让虚拟 DOM 与组件一起工作,比如React
    • 获取对实际DOM节点的引用
    • 使用带有库的虚拟 DOM,这些库可以直接改变真实的 DOM,比如 jQuery 及其插件

    原文地址:https://medium.com/@deathmood/how-to-write-your-own-virtual-dom-ee74acc13060

    作者:deathmood

    为了保证的可读性,本文采用意译而非直译。

    更多编程相关知识,请访问:编程入门!!

    以上が独自の仮想 DOM を作成するにはどうすればよいですか?手法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

    ホットAIツール

    Undresser.AI Undress

    Undresser.AI Undress

    リアルなヌード写真を作成する AI 搭載アプリ

    AI Clothes Remover

    AI Clothes Remover

    写真から衣服を削除するオンライン AI ツール。

    Undress AI Tool

    Undress AI Tool

    脱衣画像を無料で

    Clothoff.io

    Clothoff.io

    AI衣類リムーバー

    AI Hentai Generator

    AI Hentai Generator

    AIヘンタイを無料で生成します。

    ホットツール

    メモ帳++7.3.1

    メモ帳++7.3.1

    使いやすく無料のコードエディター

    SublimeText3 中国語版

    SublimeText3 中国語版

    中国語版、とても使いやすい

    ゼンドスタジオ 13.0.1

    ゼンドスタジオ 13.0.1

    強力な PHP 統合開発環境

    ドリームウィーバー CS6

    ドリームウィーバー CS6

    ビジュアル Web 開発ツール

    SublimeText3 Mac版

    SublimeText3 Mac版

    神レベルのコード編集ソフト(SublimeText3)

    PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ Mar 16, 2024 pm 12:09 PM

    PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ 今日のインターネットの急速な発展の時代において、フロントエンド開発はますます重要になっています。 Web サイトやアプリケーションのエクスペリエンスに対するユーザーの要求がますます高まっているため、フロントエンド開発者は、より効率的で柔軟なツールを使用して、応答性の高いインタラクティブなインターフェイスを作成する必要があります。フロントエンド開発の分野における 2 つの重要なテクノロジーである PHP と Vue.js は、組み合わせることで完璧なツールと見なされます。この記事では、PHP と Vue の組み合わせと、読者がこれら 2 つをよりよく理解し、適用できるようにするための詳細なコード例について説明します。

    フロントエンドの面接官からよく聞かれる質問 フロントエンドの面接官からよく聞かれる質問 Mar 19, 2024 pm 02:24 PM

    フロントエンド開発のインタビューでは、HTML/CSS の基本、JavaScript の基本、フレームワークとライブラリ、プロジェクトの経験、アルゴリズムとデータ構造、パフォーマンスの最適化、クロスドメイン リクエスト、フロントエンド エンジニアリング、デザインパターン、新しいテクノロジーとトレンド。面接官の質問は、候補者の技術スキル、プロジェクトの経験、業界のトレンドの理解を評価するように設計されています。したがって、候補者はこれらの分野で自分の能力と専門知識を証明するために十分な準備をしておく必要があります。

    簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

    JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

    Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Jan 19, 2024 am 08:37 AM

    Django は、迅速な開発とクリーンなメソッドを重視した Python で書かれた Web アプリケーション フレームワークです。 Django は Web フレームワークですが、Django がフロントエンドなのかバックエンドなのかという質問に答えるには、フロントエンドとバックエンドの概念を深く理解する必要があります。フロントエンドはユーザーが直接対話するインターフェイスを指し、バックエンドはサーバー側プログラムを指し、HTTP プロトコルを通じてデータと対話します。フロントエンドとバックエンドが分離されている場合、フロントエンドとバックエンドのプログラムをそれぞれ独立して開発して、ビジネス ロジックとインタラクティブ効果、およびデータ交換を実装できます。

    Go 言語のフロントエンド テクノロジーの探求: フロントエンド開発の新しいビジョン Go 言語のフロントエンド テクノロジーの探求: フロントエンド開発の新しいビジョン Mar 28, 2024 pm 01:06 PM

    Go 言語は、高速で効率的なプログラミング言語として、バックエンド開発の分野で広く普及しています。ただし、Go 言語をフロントエンド開発と結びつける人はほとんどいません。実際、フロントエンド開発に Go 言語を使用すると、効率が向上するだけでなく、開発者に新たな視野をもたらすことができます。この記事では、フロントエンド開発に Go 言語を使用する可能性を探り、読者がこの分野をよりよく理解できるように具体的なコード例を示します。従来のフロントエンド開発では、ユーザー インターフェイスの構築に JavaScript、HTML、CSS がよく使用されます。

    Golang とフロントエンド テクノロジーの組み合わせ: Golang がフロントエンド分野でどのような役割を果たすかを探る Golang とフロントエンド テクノロジーの組み合わせ: Golang がフロントエンド分野でどのような役割を果たすかを探る Mar 19, 2024 pm 06:15 PM

    Golang とフロントエンド テクノロジーの組み合わせ: Golang がフロントエンド分野でどのような役割を果たしているかを調べるには、具体的なコード例が必要です。インターネットとモバイル アプリケーションの急速な発展に伴い、フロントエンド テクノロジーの重要性がますます高まっています。この分野では、強力なバックエンド プログラミング言語としての Golang も重要な役割を果たします。この記事では、Golang がどのようにフロントエンド テクノロジーと組み合わされるかを検討し、具体的なコード例を通じてフロントエンド分野での可能性を実証します。フロントエンド分野における Golang の役割は、効率的で簡潔かつ学びやすいものとしてです。

    Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Jan 19, 2024 am 08:52 AM

    Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Django は、効率的でスケーラブルな Web アプリケーション フレームワークです。 MVCやMTVなど複数のWeb開発モデルをサポートし、高品質なWebアプリケーションを簡単に開発できます。 Django はバックエンド開発をサポートするだけでなく、フロントエンド インターフェイスを迅速に構築し、テンプレート言語を通じて柔軟なビュー表示を実現します。 Django はフロントエンド開発とバックエンド開発をシームレスに統合するため、開発者は学習に特化する必要がありません。

    JavaScript で HTTP ステータス コードを簡単に取得する方法 JavaScript で HTTP ステータス コードを簡単に取得する方法 Jan 05, 2024 pm 01:37 PM

    JavaScript で HTTP ステータス コードを取得する方法の紹介: フロントエンド開発では、バックエンド インターフェイスとの対話を処理する必要があることが多く、HTTP ステータス コードはその非常に重要な部分です。 HTTP ステータス コードを理解して取得すると、インターフェイスから返されたデータをより適切に処理できるようになります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法と、具体的なコード例を紹介します。 1. HTTP ステータス コードとは何ですか? HTTP ステータス コードとは、ブラウザがサーバーへのリクエストを開始したときに、サービスが

    See all articles