JSX 默认是低效的……但是……

JSX 默认是低效的……但是……

Photo by 哈维尔·马泽奥 on 不飞溅

随着不可否认的增强型 DX 及其在框架和工具中的极受欢迎, JSX 规格 我已经关注了很长一段时间了,这篇文章的目标是分析它的优缺点,还提出一种改进的默认解析,以向后兼容的方式修复它的低效率。

什么是 JSX?

我相信阅读这篇文章的人都不需要答案,但是,既然我们在这里, JSX 是一个 DSL 直接通过 JS 或任何其他环境表示类似 XML 的视图,使用运行时插值。

 <Component>  
 <tag static="property" dynamic={property}>  
 静态内容  
 <inner-tag />  
 <>  
 静态片段内容  
 {'动态' ||<content /> }  
 </>  
 {'动态' ||<content /> }  
 </tag>  
 </Component>

如果您不熟悉此主题,请考虑使用 awesome 通天塔游乐场 反驳我写下的一切

JSX 是做什么用的?

在 Web 上,它是 模板文字标签 基于库的库,反之亦然,除了它通常不需要插件在我们选择的 IDE 上突出显示其内容,而且它也可以用于并不真正关心 ECMAScript 或 Web 规范的原生平台一般来说。

事实上,最好的功能之一 JSX 这些天提供的,是它也能够将自己转换为本机平台指令或 API,而在这些平台中强制执行模板文字标签标准和解决方案几乎毫无意义并且实际上很慢(在采用和执行方面)。

是什么让 JSX 默认效率低下

JSX 目前被大多数人改造的方式“ 变压器 ”在那里,关于它的使用或开发者意图的大量信息是“ 迷失在翻译中 ”!

以最初的代码片段为例,看看它是这样转换的:

 反应.createElement(  
 Component, // 这是一个静态组件  
 无效的,  
 反应.createElement(  
 "tag", // 这是一个静态标签  
 {  
 static: "property", // 这是一个静态属性  
 动态:属性//这需要解决  
 },  
 "静态内容", // 这是一个静态文本节点  
 反应.createElement(  
 "inner-tag", // 这是一个静态元素  
 无效的  
 ),  
 反应.createElement(  
 React.Fragment, // 这是一个静态片段  
 无效的,  
 "静态片段内容", // 这是一个静态文本节点  
 '动态' || React.createElement("内容", null)  
 // 这是片段中唯一的动态部分  
 ),  
 '动态' || React.createElement("内容", null)  
 // 这是组件唯一的动态部分  
 )  
 )

要了解为什么默认情况下它效率低下,让我们看一下 React.createElement 签名:

 函数 createElement(kind, props, ...children) {}

确实,这个签名涵盖了所有可能的 XML-ish 树相关情况,但也确实没有办法理解:

  • 该组件是静态的并且在解析时是众所周知的吗?
  • 它的任何孩子在解析时是静态的并且众所周知吗?
  • 属性是否都是动态的,需要在未来区分它们的价值?
  • 那个孩子以后有什么要处理的吗?

目前所有答案都是“ 也许 ”,我可以尝试所有默认的转换器,这真是太糟糕了!

模板文字标签如何在这里轻松获胜?

 // 整个模板获得一个唯一的、内存友好的标识符  
 // 作为作为 html(template, ...values) 传递的模板引用  
 // 注意:只有插值在值内!  
 html`  
 <Component>  
 <tag static="property" dynamic=${property}>  
 静态内容  
 <inner-tag />  
 <>  
 静态片段内容  
 ${'动态' ||<content /> }  
 </>  
 ${'动态' ||<content /> }  
 </tag>  
 </Component>  
 `;

模板文字标签是“ 每个代码解析唯一 ”,这意味着即使函数中有一些标签,它们的引用也会随着时间的推移而保留:

 常量模板 = 新的弱集;  
 const tag = (template, ...values) => {  
 if (!templates.has(template)) {  
 console.log('新模板');  
 模板。添加(模板);  
 }  
 返回值;  
 }; 功能组件(值){  
 返回标签`<div>随便 ${value}</div> `;  
 } 组件(1);  
 // 日志:“新模板”  
 // 日志:数组 [1] 组件(2);  
 // 日志:数组 [2] 组件(3);  
 // 日志:数组 [3]

简而言之,每个模板文字标签都是“ 永远独一无二(直到 GC) ” 在任何范围内,这就是 uhtml , 点亮 或其他库,能够开箱即用地存在并表现得非常好!

… 但 …

模板文字标签如何在这里容易丢失?

在一个 鸣叫

我探索了内置扩展,因此消费者只能将我的逻辑绑定到某些元素类型,但是如果我明天想更改元素怎么办?我可以用一个组件来做到这一点,而不会对消费者产生任何影响。我不能用内置插件做到这一点。一个组件封装了所有这些东西—— 珍娜

组件化 等式的一部分在基于模板文字标签的解决方案中确实完全丢失了,因为模板只是字符串,因此不知道周围的范围,并且不翻译。

 html`<Component /> `

在 ECMASccript 标准中没有特殊含义,因此不可能授予,如果没有工具做奇怪的事情,无法从恶作剧中获得开发人员的意图,同样的 DX React 或一般 JSX 用户,期望。

当然,模板字面量可以精确指出模板中需要额外关注未来、反应式、更新的每一个插值,但仅此功能对于当前基于 JSX 的行业来说并不是非常引人注目。

反正谁在乎……

好吧,作为在其他任何人之前率先推出基于模板文字标签的解决方案的人(是的, 超HTML 几个月前就存在 点亮 甚至出版),并作为提出的 这些问题的替代方案 ,我确实关心“ 事实标准 ” 以及他们的收养,因此 将所有功能融合在一起 一直是我终于有信心分享的个人研究领域,所以请耐心等待……我到了!

更好的 JSX 转换

如果 JSX 可以对其定义良好的插值有提示怎么办?

这是我在 Babel 级别提出的一个话题,但我觉得它应该覆盖更广泛的受众,首先是 React 团队。

让我们看一个已经改进的转换示例:

 反应.createElement(  
 零件,  
 无效的,  
 反应.createElement(  
 “标签”,  
 {  
 静态:“财产”,  
 动态:React.interpolation(property)  
 // 这是运行时唯一改变的属性  
 },  
 “静态内容”,  
 反应.createElement(  
 “一天之内”,  
 无效的  
 ),  
 反应.createElement(  
 反应片段,  
 无效的,  
 "静态片段内容",  
 React.interpolation(  
 '动态' ||  
 React.createElement("内容", null))  
 // 这是片段中唯一需要更新的部分  
 ),  
 React.interpolation(  
 '动态' ||  
 React.createElement("内容", null))  
 // 这是唯一需要更新的组件部分  
 )  
 )

不仅我已经开始了 通过 Babel 问题进行的对话 ,这个围绕插值的可选编译指示已经可以加速大量不必要的工作 在家 基于解决方案,因为最终有一个明确的提示,说明什么是动态的,什么是静态的。

如果您想知道如何选择加入 插值 助手看起来像,这是我目前最好的客人:

 // 基本插值  
 类插值{  
 构造函数(_){  
 这._ = _;  
 }  
 的价值() {  
 返回这个._;  
 }  
 }  
 React.interpolation = value => new Interpolation(value);

这将允许,尤其是在 道具 级别,区分 道具 需要进一步处理的,以及 道具 不(静态)。

要了解如果一个 支柱 是否静态, 道具类型[key] 就足够了,因为 细绳 是静态的结果类型 道具 , 尽管 目的 是动态的 道具 , 在哪里 prop.valueOf() 总是会用这些返回初始属性,也向后兼容。

但是关于 **{...道具}** ?

好吧,对于那些我们需要更好的转换的人:

 // ...道具变换  
 /*#__PURE__*/ function _extends(target) {  
 for(var i = 1; i < arguments.length; i++){  
 var 来源 = 参数 [i];  
 for(源代码中的var键){  
 if (Object.prototype.hasOwnProperty.call(source, key)) {  
 目标[键] = 新插值(源[键]); // 这里!  
 }  
 }  
 }  
 返回目标;  
 }

这会将任何属性标记为动态的,因为这就是 ...道具 在幕后做,所以任何图书馆都可以决定如何处理这些信息

但是关于 **...孩子们** ?

再一次, 孩子的类型 存在 细绳 会提示静态内容,而 child.valueOf() 会将实际内容作为插值或众所周知的组件引入,其中差异,仍然是一次性解析,将通过快速和廉价的方式来揭示 插值的子实例 查看。

请记住,这些都是在任何组件的引导时执行的一次性检查,因为以下更新将被映射并且尽可能快速、便宜。

也使用模板文字标签

到目前为止,我的提案解决了静态 VS 运行时内容,但它并没有解决每个 JSX 模板也是静态的并且是众所周知的,但没有必要不这样对待它:

 // 通过使用标签,我们可以独立地限制堆栈操作  
 // 通过 React.createElement(template, ...values) 引用。  
 // 这可以直接返回一次 createElement.bind(template) React.createElement``( // 使外部组件静态且唯一  
 零件,  
 无效的,  
 反应.createElement(  
 “标签”,  
 {  
 静态:“财产”,  
 动态:React.interpolation(property)  
 },  
 “静态内容”,  
 反应.createElement(  
 “一天之内”,  
 无效的  
 ),  
 反应.createElement(  
 反应片段,  
 无效的,  
 "静态片段内容",  
 React.interpolation(  
 '动态' ||  
 React.createElement``("content", null)  
 // 使内部组件静态且唯一  
 )  
 // 仍然是唯一需要解决的部分  
 ),  
 React.interpolation(  
 '动态' ||  
 React.createElement``("content", null)  
 // 使内部组件静态且唯一  
 )  
 // 仍然是唯一需要解决的部分  
 )  
 )

现在,因为我们使用的实用程序与常规、旧式、方法或基于模板文字标签的实用程序相同,我们可能想知道该帮助程序将来会是什么样子?

 // 每个静态已知的 JSX 外部模板使用一个 WeakMap  
 常量模板 = 新的 WeakMap; React.createElement = function createElement(template) {  
 // 称为模板标签(JSX 向后兼容)  
 if (arguments.length === 1) {  
 让绑定 = 模板.get(模板);  
 // 如果未知则绑定一次回调  
 如果(!绑定){  
 绑定 = createElement.bind(模板);  
 模板.set(模板,绑定);  
 }  
 // 返回一个始终相同的绑定引用  
 返回绑定;  
 }  
 // 允许为当前模板创建一个众所周知的堆栈  
 if (templates.has(this)) {  
 // 使用模板上下文作为参考进行任何智能解析  
 // 从头开始​​创建一个堆栈,更新前一个堆栈,等等  
 返回 runtimeElement.apply(this, arguments);  
 }  
 // 这里是一个常规的创建元素案例,意味着它是静态内容  
 return staticElement.apply(null, arguments);  
 };

默认向后兼容

该提案将允许在以前的默认值之间迁移 JSX 通过 pragma 定义选择模板文字标签版本,或拥有一个 pragma插值 在混合中,如果启用,使用 React.interpolation 通过课程,而不是无操作。

作为总结

这篇文章显示了周围的缺陷 JSX 但它也提出了一种新的方式 JSX 不仅可以被转换器使用,而且可以在默认转换后的运行时有效地使用。

最后但并非最不重要的一点是,你们中的一些人可能会想:“ 但是为什么不使用 Solid-JS 转换器,因为它更好?

这篇文章的重点不是要优于 JSX 作为单个库/框架转换器,是为了使默认转换器更好,更具竞争力,与其他可能做这个或那个来修复 JSX 问题和/或性能的东西相比

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/40164/39450109

posted @ 2022-10-01 09:40  哈哈哈来了啊啊啊  阅读(32)  评论(0编辑  收藏  举报