揭秘JSX:从零开始打造自己的JSX解析器
了解JSX:由Dall-e编写
虽然它不是 Web 标准,Web Components 在某种程度上试图取代它,但 JSX 是与 React 一起出现的出色技术,它简化了我们同时编写 HTML 和 JavaScript 的方式。
但它是怎么真正工作的呢?我的意思是,我们可以返回JSX,但这并不是标准的JavaScript,对吧?所以它究竟是怎么运作的呢?背后到底有什么魔法呢?
我个人非常喜欢技术“直接好用”,但如果我的工作离不开它,我都会尽量去弄懂它。其中一个办法就是试着逆向分析它,看看它是怎么运作的,并试着自己动手做一份。
你可以通过这个过程学到很多东西!
所以在这篇文章里,我将展示如何编写你自己的 JSX 解析器,让它能够将一个 JSX “组件” 转换为返回有效 HTML 的 JavaScript 函数。
走啦!
这段我们要解析的 JSX 代码
我们先从最后开始,看看我们要处理的JSX文件。
如果你用 React 写这段代码,会是这样的。
说实话,唯一会改变的部分就是初始的导入。当我们需要用JSX时,我们实际上会明白为什么需要导入React。
虽然解析本身有点麻烦,但这个背后的逻辑其实很简单。如果你去看 Rect 的文档,他们就会展示解析 JSX 的结果。
从 React 的文档[1](https://reactjs.org/docs/introducing-jsx.html)截图
你知道,你实际上是把每个JSX元素转换成React.createElement
的调用。没错!这也是为什么即使你表面上没有直接用到React
,也需要导入它,一旦解析后,最终生成的JavaScript代码确实需要用到它。
现在那个谜已经解决了,我们继续。
第一个属性是待创建元素的标签名。第二个属性包含该元素的所有属性。其余属性(可以有一个或多个)将作为此元素的直接子元素,它们可以是纯文本或其它元素。
就这样完了,这个任务的挑战是:
- 将 JSX 代码捕获到 JavaScript 中。
- 将其解析成一棵树状结构,这样我们就能遍历和查询。
- 将该结构翻译成 JavaScript 代码(文本),并将其写入替代 JSX 的位置。
- 将步骤 3 的结果保存到一个带有
.js
扩展名的文件中。
开始写代码了!
从组件中提取并解析 JSX 代码第一步是先从组件中提取 JSX 并将其解析成树状结构。
注:此处的 JSX 保留英文原貌,因为它是一个技术术语。
当然,这是两步,但我们将会一起做。
首先,我们需要做的是读取JSX文件,然后使用正则(确实,我们将在这篇文章中使用几个正则),来捕获JSX代码。
一旦我们拿到它,就可以用HTML解析器来解析它。
记住这里我们可以这样做,因为在这一点上我们只关心结构,而不关心JSX的实际特性。我们使用Node的fs
模块来读取文件,以及node-html-parser
包。
这个函数大致是这样的:
此功能使用正则(RegExp)来查找函数 (...)
部分中第一个组件的起始标签。在第 10 行,我们调用了 parse
函数,,该函数返回一个 root
元素,其中 firstChild
是我们 JSX 根元素(在这种情况下,即包裹的 div
)。
我们现在有了树状结构,让我们开始将其转换成代码。接下来,我们将调用 translate
函数来完成这个转换。
由于我们的树状结构层次有限,我们可以安全地使用递归算法遍历这个树状结构。
以下就是该函数的样子,我们下面来看一下。
首先,我们会遍历所有子项,并对它们调用 translate
函数。如果它们为空或没有子项,它将返回 null
,我们会在第 7 行过滤掉这些结果。
处理完孩子们的问题后,让我们看看第9行,我们在那里快速检查节点类型。如果类型是3,这意味着这是一个纯文本节点,所以我们返回纯文本内容。
为什么我们要调用 parseText
函数?因为在文本节点内部,我们也需要寻找这样的 JSX 表达式。因此这个函数会负责检查并在必要时正确处理返回的字符串。
然后,换句话说,也就是说我们实际上处理的不是一个文本节点,我们会获取标签名(第14行),然后解析属性(第16行)。解析属性意味着我们会把那个原始字符串转换成正确的JSON格式。
这就是第 18 行所做的事情,即生成带有正确参数的 createElement
调用,如该行所示。
记得我们在写代码,而不是真的在运行它。所有的代码都写在字符串里。
最后,关于这个功能需要注意的一个细节是,生成的代码中调用了来自 MyLib
模块的 createElement
方法。因此,JSX 文件中需要包含 import * as MyLib from './MyLib.js'
。
我们现在必须开始用字符串替换每个元素中的JSX表达式,包括文本节点和属性。
你喜欢刚才读的内容吗?试试订阅我的免费通讯,在那里我会与大家分享我在IT行业20年的经验与心得。加入“一位老程序员的随笔”!
解析这些表达式在这个实现里,我支持的 JSX 表达式是最简单的那种。比如在下面的例子中,您可以在这些表达式里插入 JS 变量,它们仍将保持为变量在最终结果里。
这里有几个相关的函数:
如果有插值的话,即在大括号里的变量,那么我们就调用 replaceInterpolation
函数,这个函数会找到所有匹配的插值,并且把它们替换为格式正确的字符串。这样在写入 JS 文件时,变量名会以一种方式保留下来,生成一个有效的 JS 变量。
我们也会用这些函数来处理属性对象。因为我们返回JS代码时用了JSON.stringify
方法,它会把所有值变成字符串,基本上所有的值都会被转换成字符串,尤其是我们单独定义的那些变量。所以我们会解析stringify
返回的字符串,并确保正确替换插值变量。
你可以查看 getAttrs
函数 这里,以了解它是怎么做的。
我们现在来看看解析我们JSX文件后得到的代码。
JavaScript代码阅读并解析我的 JSX 代码后,结果如下:
这段代码中最有趣的部分是生成的createElement
调用。你可以看到它们的嵌套方式,它们引用了我在JSX文件中插入的变量。
如果我们运行这段代码,输出将会是这样的:
但是最后一个问题是还没有得到回答的:createElement
方法又是怎么实现的呢?好吧,我这里也有一个简化版的实现:
基本上,我使用tag
值创建一个包装元素,如果有属性的话,就添加它们,最后遍历子元素列表(该列表包含所有的子元素),在这个过程中,我将这些值作为字符串返回(见第9行)。
就这样,魔术揭晓了!
JSX 是我最喜欢的在 JS 文件内处理和创建 HTML 的技术之一,它确实简化了这个过程。
当然,你可以直接用createElement
方法来写代码,但这对于复杂的应用来说既不简单也不好看。
记得你可以在这里查看此项目的完整源代码,并且如果你有任何问题,可以在下面留言或提问,我们可以进一步讨论!
基于像乐高积木一样可重复使用的组件构建应用程序Bit的开源工具帮助着250,000+开发者们构建包含组件的应用。
将任何用户界面、功能或页面转化为一个可复用组件,并在您的应用程序之间共享它。这样更便于协作,构建速度更快。
→了解更多
将应用拆分为组件可以让你更轻松地进行应用开发,享受你想要的工作流程的最佳体验。
→ 微前端架构我们如何构建微前端 - Medium
→ 设计体系(我们如何构建设计体系) → 代码共享与重用:文章 单仓库示例:单仓库 要了解更多 我们如何构建微前端应用通过构建微前端应用来加快我们的网页开发进程和扩展开发规模。 我们如何构建组件设计系统通过构建组件来标准化并规模化我们的用户界面开发流程。 如何重用React组件最终,你完成了在你的应用程序中为新闻通讯创建了一个很棒的输入字段的任务。你对此感到很满意。 5 种方式构建 React 单仓库构建一个生产级别的 React 单仓库:从快速构建到代码共享和依赖管理,本文将介绍五种方式。blog.bitsrc.io 如何使用 Bit 创建可组合的 React 应用 在此指南中,您将学习如何使用 Bit 构建并部署一个完整的可组合 React 应用。构建一个完整的可组合 React 应用……bit.cloud共同学习,写下你的评论
评论加载中...
作者其他优质文章