VanillaJSでのReactとJSXのエミュレート
公開: 2022-03-11フレームワークを嫌う人はほとんどいませんが、あなたがその1人であっても、注意を払い、生活を少し楽にする機能を採用する必要があります。
私は過去にフレームワークの使用に反対しました。 ただし、最近、いくつかのプロジェクトでReactとAngularを使用した経験があります。 コードエディタを開いてAngularでコードを書き始めた最初の数回は、奇妙で不自然に感じました。 特に、フレームワークを使用せずに10年以上コーディングした後。 しばらくして、私はこれらの技術を学ぶことを約束することにしました。 すぐに大きな違いが1つ明らかになりました。それは、DOMの操作が非常に簡単で、必要に応じてノードの順序を調整するのが非常に簡単で、UIを構築するのにページやコードのページを必要としませんでした。
フレームワークやアーキテクチャに接続されない自由を今でも望んでいますが、1つでDOM要素を作成する方がはるかに便利であるという事実を無視することはできませんでした。 そこで、バニラJSでの体験をエミュレートする方法を検討し始めました。 私の目標は、Reactからそれらのアイデアのいくつかを抽出し、同じ原則をプレーンJavaScript(バニラJSと呼ばれることが多い)に実装して、開発者の生活を少し楽にする方法を示すことです。 これを実現するために、GitHubプロジェクトを閲覧するための簡単なアプリを作成しましょう。
JavaScriptを使用してフロントエンドを構築する方法に関係なく、DOMにアクセスして操作します。 このアプリケーションでは、各リポジトリの表現(サムネイル、名前、説明)を作成し、それをリスト要素としてDOMに追加する必要があります。 GitHubSearchAPIを使用して結果を取得します。 そして、JavaScriptについて話しているので、JavaScriptリポジトリを検索してみましょう。 APIをクエリすると、次のJSON応答が返されます。
{ "total_count": 398819, "incomplete_results": false, "items": [ { "id": 28457823, "name": "freeCodeCamp", "full_name": "freeCodeCamp/freeCodeCamp", "owner": { "login": "freeCodeCamp", "id": 9892522, "avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4", "gravatar_id": "", "url": "https://api.github.com/users/freeCodeCamp", "site_admin": false }, "private": false, "html_url": "https://github.com/freeCodeCamp/freeCodeCamp", "description": "The https://freeCodeCamp.org open source codebase "+ "and curriculum. Learn to code and help nonprofits.", // more omitted information }, //... ] }
Reactのアプローチ
Reactを使用すると、HTML要素をページに非常に簡単に書き込むことができます。これは、純粋なJavaScriptでコンポーネントを作成するときに常に必要としていた機能の1つです。 ReactはJSXを使用します。これは通常のHTMLと非常によく似ています。
ただし、それはブラウザが読み取るものではありません。
内部的には、ReactはJSXをReact.createElement
関数への一連の呼び出しに変換します。 GitHub APIの1つのアイテムを使用したJSXの例を見て、それが何に変換されるかを見てみましょう。
<div className="repository"> <div>{item.name}</div> <p>{item.description}</p> <img src={item.owner.avatar_url} /> </div>;
; React.createElement( "div", { className: "repository" }, React.createElement( "div", null, item.name ), React.createElement( "p", null, item.description ), React.createElement( "img", { src: item.owner.avatar_url } ) );
JSXはとてもシンプルです。 通常のHTMLコードを記述し、中括弧を追加してオブジェクトからデータを挿入します。 括弧内のJavaScriptコードが実行され、結果のDOMに値が挿入されます。 JSXの利点の1つは、Reactが仮想DOM(ページの仮想表現)を作成して、変更と更新を追跡することです。 HTML全体を書き直す代わりに、Reactは情報が更新されるたびにページのDOMを変更します。 これは、Reactが解決するために作成された主要な問題の1つです。
jQueryアプローチ
開発者はjQueryをよく使用していました。 それはまだ人気があり、純粋なJavaScriptのソリューションにかなり近いため、ここで言及したいと思います。 jQueryは、DOMにクエリを実行することにより、DOMノード(またはDOMノードのコレクション)への参照を取得します。 また、その参照を、その内容を変更するためのさまざまな機能でラップします。
jQueryには独自のDOM構築ツールがありますが、私が実際に最もよく目にするのはHTMLの連結だけです。 たとえば、 html()
関数を呼び出すことにより、選択したノードにHTMLコードを挿入できます。 jQueryのドキュメントによると、クラスdemo-container
を使用してdiv
ノードの内容を変更する場合は、次のように行うことができます。
$( "div.demo-container" ).html( "<p>All new content.<em>You bet!</em></p>" );
このアプローチにより、DOM要素を簡単に作成できます。 ただし、ノードを更新する必要がある場合は、必要なノードをクエリするか、(より一般的には)更新が必要なときはいつでもスニペット全体の再作成にフォールバックする必要があります。
DOMAPIアプローチ
ブラウザのJavaScriptには、ページ内のノードの作成、変更、および削除に直接アクセスできるDOMAPIが組み込まれています。 これはReactのアプローチに反映されており、DOM APIを使用することで、そのアプローチのメリットに一歩近づくことができます。 実際に変更する必要があるページの要素のみを変更しています。 ただし、Reactは個別の仮想DOMも追跡します。 仮想DOMと実際のDOMの違いを比較することで、Reactは変更が必要な部分を特定できます。
これらの追加の手順は便利な場合もありますが、常にそうとは限りません。DOMを直接操作する方が効率的です。 _document.createElement_
関数を使用して新しいDOMノードを作成できます。これにより、作成されたノードへの参照が返されます。 これらの参照を追跡することで、更新が必要な部分を含むノードのみを簡単に変更できます。
JSXの例と同じ構造とデータソースを使用して、次の方法でDOMを構築できます。
var item = document.createElement('div'); item.className = 'repository'; var nameNode = document.createElement('div'); nameNode.innerHTML = item.name item.appendChild(nameNode); var description = document.createElement('p'); description.innerHTML = item.description; item.appendChild(description ); var image = new Image(); Image.src = item.owner.avatar_url; item.appendChild(image); document.body.appendChild(item);
頭に浮かぶのがコード実行の効率だけである場合、このアプローチは非常に優れています。 ただし、効率は、実行速度だけでなく、保守のしやすさ、スケーラビリティ、および可塑性でも測定されます。 このアプローチの問題は、非常に冗長で、複雑な場合があることです。 基本的な構造を構築しているだけの場合でも、一連の関数呼び出しを作成する必要があります。 2番目の大きな欠点は、作成および追跡される変数の数が非常に多いことです。 たとえば、作業しているコンポーネントに独自の30個のDOM要素が含まれている場合、30個の異なるDOM要素と変数を作成して使用する必要があります。 それらのいくつかを再利用して、保守性と可塑性を犠牲にしてジャグリングを行うことができますが、それは本当に厄介になり、本当にすぐになります。

もう1つの重大な欠点は、記述する必要のあるコードの行数によるものです。 時間の経過とともに、ある親から別の親に要素を移動することがますます難しくなります。 それは私がReactから本当に感謝していることの1つです。 JSX構文を表示して、数秒でどのノードがどこに含まれているかを取得し、必要に応じて変更できます。 そして、最初は大したことではないように思えるかもしれませんが、ほとんどのプロジェクトには、より良い方法を探すための絶え間ない変更があります。
提案された解決策
DOMを直接操作すると機能し、作業が完了しますが、特にHTML属性とネストノードを追加する必要がある場合は、ページの作成が非常に冗長になります。 したがって、JSXのようなテクノロジーを使用することの利点のいくつかを把握し、私たちの生活をよりシンプルにするというアイデアがあります。 複製しようとしている利点は次のとおりです。
- DOM要素の作成が読みやすく、変更しやすいように、HTML構文でコードを記述します。
- Reactの場合のように仮想DOMに相当するものを使用していないため、関心のあるノードを簡単に示して追跡する方法が必要です。
これは、HTMLスニペットを使用してこれを実現する単純な関数です。
Browser.DOM = function (html, scope) { // Creates empty node and injects html string using .innerHTML // in case the variable isn't a string we assume is already a node var node; if (html.constructor === String) { var node = document.createElement('div'); node.innerHTML = html; } else { node = html; } // Creates of uses and object to which we will create variables // that will point to the created nodes var _scope = scope || {}; // Recursive function that will read every node and when a node // contains the var attribute add a reference in the scope object function toScope(node, scope) { var children = node.children; for (var iChild = 0; iChild < children.length; iChild++) { if (children[iChild].getAttribute('var')) { var names = children[iChild].getAttribute('var').split('.'); var obj = scope; while (names.length > 0) { var _property = names.shift(); if (names.length == 0) { obj[_property] = children[iChild]; } else { if (!obj.hasOwnProperty(_property)){ obj[_property] = {}; } obj = obj[_property]; } } } toScope(children[iChild], scope); } } toScope(node, _scope); if (html.constructor != String) { return html; } // If the node in the highest hierarchy is one return it if (node.childNodes.length == 1) { // if a scope to add node variables is not set // attach the object we created into the highest hierarchy node // by adding the nodes property. if (!scope) { node.childNodes[0].nodes = _scope; } return node.childNodes[0]; } // if the node in highest hierarchy is more than one return a fragment var fragment = document.createDocumentFragment(); var children = node.childNodes; // add notes into DocumentFragment while (children.length > 0) { if (fragment.append){ fragment.append(children[0]); }else{ fragment.appendChild(children[0]); } } fragment.nodes = _scope; return fragment; }
アイデアはシンプルですが強力です。 作成するHTMLを文字列として関数に送信し、HTML文字列で、参照を作成するノードにvar属性を追加します。 2番目のパラメーターは、これらの参照が格納されるオブジェクトです。 指定されていない場合は、返されたノードまたはドキュメントフラグメントに「nodes」プロパティを作成します(最上位の階層ノードが複数ある場合)。 すべてが60行未満のコードで実行されます。
この関数は次の3つのステップで機能します。
- 新しい空のノードを作成し、その新しいノードでinnerHTMLを使用して、DOM構造全体を作成します。
- ノードを反復処理し、var属性が存在する場合は、その属性を持つノードを指すプロパティをスコープオブジェクトに追加します。
- 階層の最上位ノード、または複数ある場合はドキュメントフラグメントを返します。
では、例をレンダリングするためのコードはどのようになりますか?
var UI = {}; var template = ''; template += '<div class="repository">' template += ' <div var="name"></div>'; template += ' <p var="text"></p>' template += ' <img var="image"/>' template += '</div>'; var item = Browser.DOM(template, UI); UI.name.innerHTML = data.name; UI.text.innerHTML = data.description; UI.image.src = data.owner.avatar_url;
まず、作成されたノードへの参照を格納するオブジェクト(UI)を定義します。 次に、使用するHTMLテンプレートを文字列として作成し、ターゲットノードを「var」属性でマークします。 その後、テンプレートと参照を格納する空のオブジェクトを使用して関数Browser.DOMを呼び出します。 最後に、保存された参照を使用して、ノード内にデータを配置します。
このアプローチでは、DOM構造の構築とデータの挿入を別々のステップに分離することで、コードを整理して適切に構造化するのに役立ちます。 これにより、DOM構造を個別に作成し、データが利用可能になったときにデータを入力(または更新)することができます。
結論
フレームワークに切り替えて制御を引き継ぐというアイデアを嫌う人もいますが、これらのフレームワークがもたらすメリットを認識することが重要です。 彼らがとても人気があるのには理由があります。
フレームワークは必ずしもあなたのスタイルやニーズに合うとは限りませんが、一部の機能や手法を採用したり、エミュレートしたり、場合によってはフレームワークから切り離したりすることもできます。 翻訳では常に失われるものもありますが、フレームワークが負担するコストのごく一部で多くのことを取得して使用できます。