在Vanilla JS中模拟React和JSX

本文概述

  • React的方法
  • jQuery方法
  • DOM API方法
  • 可用的解决方案
  • 总结
很少有人不喜欢框架, 但是即使你是其中之一, 也应注意并采用使生活更轻松的功能。
过去, 我反对使用框架。但是, 最近, 我在一些项目中有使用React和Angular的经验。头几次, 我打开代码编辑器并开始用Angular编写代码, 这感觉很奇怪而且很不自然。特别是经过十多年的编码而不使用任何框架。一段时间后, 我决定致力于学习这些技术。很快就发现了一个很大的区别:操作DOM非常容易, 在需要时可以很容易地调整节点的顺序, 并且不需要花很多页面代码来构建UI。
尽管我仍然更喜欢不附加到框架或体系结构上的自由, 但是我不能忽略这样的事实, 即在一个框架中创建DOM元素会更加方便。因此, 我开始研究模仿香草JS体验的方法。我的目标是从React中提取一些想法, 并演示如何在纯JavaScript(通常称为Vanilla JS)中实现相同的原理, 从而使开发人员的生活更加轻松。为此, 我们构建一个简单的应用程序来浏览GitHub项目。
在Vanilla JS中模拟React和JSX

文章图片
我们正在构建的应用程序。
无论我们使用JavaScript构建前端的哪种方式, 我们都将访问和操作DOM。对于我们的应用程序, 我们将需要构造每个存储库的表示形式(缩略图, 名称和描述), 并将其作为列表元素添加到DOM中。我们将使用GitHub Search API来获取结果。而且, 由于我们在谈论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编写组件时一直希望拥有的功能之一。 React使用JSX, 它与常规HTML非常相似。
但是, 那不是浏览器读取的内容。
在幕后, React将JSX转换为对React.createElement函数的一堆调用。让我们来看一个使用GitHub API中的一项的JSX示例, 并查看其含义。
< div className="repository"> < div> {item.name}< /div> < p> {item.description}< /p> < img src=http://www.srcmini.com/{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的优点之一是React可以创建一个虚拟DOM(页面的虚拟表示)来跟踪更改和更新。每当信息更新时, React都不会修改整个HTML, 而是修改页面的DOM。这是React所要解决的主要问题之一。
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元素变得容易。但是, 当我们需要更新节点时, 我们需要查询所需的节点, 或者(通常)在需要更新时退回到重新创建整个代码段。
DOM API方法 浏览器中的JavaScript具有内置的DOM API, 使我们可以直接访问创建, 修改和删除页面中的节点。这在React的方法中得到了体现, 并且通过使用DOM API, 我们比该方法的优势更近了一步。我们仅修改实际需要更改的页面元素。但是, React也会跟踪单独的虚拟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 = http://www.srcmini.com/item.owner.avatar_url; item.appendChild(image); document.body.appendChild(item);

如果你唯一想到的是代码执行的效率, 那么这种方法非常好。但是, 效率不仅仅以执行速度来衡量, 还以维护, 易扩展性和可塑性来衡量。这种方法的问题在于它非常冗长, 有时令人费解。即使我们只是构造一个基本结构, 我们也需要编写一堆函数调用。第二个大缺点是创建和跟踪的变量数量庞大。假设你正在使用的组件包含30个DOM元素, 则需要创建和使用30个不同的DOM元素和变量。你可以重用其中的一些, 并且以可维护性和可塑性为代价进行一些处理, 但是它可能会变得非常杂乱, 非常迅速。
另一个明显的缺点是由于需要编写的代码行数众多。随着时间的流逝, 将元素从一个父对象移动到另一个父元素的难度越来越大。我从React真的很欣赏这件事。我可以查看JSX语法, 并在几秒钟内获得包含哪个节点, 位于何处, 并根据需要进行更改。而且, 虽然起初似乎没什么大不了, 但大多数项目都有不断的变化, 这会让你寻找更好的方法。
可用的解决方案 直接使用DOM可以工作并完成工作, 但是这也使得构建页面非常冗长, 尤其是当我们需要添加HTML属性和嵌套节点时。因此, 该想法将是捕获使用JSX之类的技术的一些好处, 并使我们的生活更简单。我们尝试复制的优势如下:
  1. 用HTML语法编写代码, 以便DOM元素的创建变得易于阅读和修改。
  2. 由于我们没有使用类似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 nodesvar _scope = scope || {}; // Recursive function that will read every node and when a node // contains the var attribute add a reference in the scope objectfunction 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 itif (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属性。第二个参数是一个对象, 这些引用将存储在该对象中。如果未指定, 我们将在返回的节点或文档片段上创建” nodes” 属性(如果最高层级节点不止一个)。一切都用不到60行代码完成。
该功能分为三个步骤:
  1. 创建一个新的空节点, 并在该新节点中使用innerHTML创建整个DOM结构。
  2. 遍历节点, 如果var属性存在, 则在范围对象中添加一个属性, 该属性指向具有该属性的节点。
  3. 返回层次结构中的最高节点, 如果有多个节点, 则返回一个文档片段。
那么, 用于呈现示例的代码现在看起来如何?
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 = http://www.srcmini.com/data.owner.avatar_url;

【在Vanilla JS中模拟React和JSX】首先, 我们定义对象(UI), 我们将在其中存储对创建的节点的引用。然后, 我们将构成要使用的HTML模板作为字符串, 用” var” 属性标记目标节点。之后, 我们使用模板和将存储引用的空对象调用函数Browser.DOM。最后, 我们使用存储的引用将数据放置在节点内。
这种方法还将构建DOM结构和将数据插入单独的步骤分开, 这有助于保持代码的组织性和结构良好。这使我们能够分别创建DOM结构并在数据可用时填充(或更新)数据。
总结 尽管我们中的某些人不喜欢切换到框架并移交控制权的想法, 但重要的是, 我们必须认识到框架带来的好处。它们如此受欢迎是有原因的。
尽管框架可能并不总是适合你的风格或需求, 但是可以采用, 模拟甚至有时与框架分离一些功能和技术。有些事情在翻译中总是会丢失, 但是可以获取和使用很多东西, 而这只花费了框架成本的一小部分。

    推荐阅读