宝哥软件园

一个让小程序支持JSX语法的新想法

编辑:宝哥软件园 来源:互联网 时间:2021-08-22

React社区一直在探索使用React语法开发小程序的方法,其中Taro和nanachi是著名的项目。然而,用React语法开发小程序的难点在于,JSX本质上是JS,与小程序的静态模板相比过于灵活。本文提到的新思路是处理JSX语法的新思路,是一种更动态的处理思路。与现有的方案相比,它基本上不限制任何JSX的写作,允许你以真实的反应方式处理小程序。希望这个新想法能给任何对用React开发小程序感兴趣的人带来启发。

现有想法的局限性

在介绍新思想之前,让我们先来看看Taro(最新的1.3版本),nanachi是如何在applet端处理JSX语法的。简单来说,React代码主要是通过在编译阶段将JSX转换成等价的小程序wxml在小程序端运行。

例如,如反应逻辑表达式:

Xx文本Hello/Text将被转换为等效的小程序wx:if指令:

text wx : if=' { { xx } } ' hello/text这种方式把JSX的处理主要放在编译阶段,它依赖于编译阶段的信息收集。例如,它必须识别逻辑表达式,然后进行相应的wx: if转换处理。

编译阶段有哪些问题和局限性?我们用下面的例子来说明:

class app extends react.component { render(){ const a=TextHello/Textconst b=a return(view { b }/view)} }首先我们声明const a=texthello/text,然后给b赋值,我们来看看最新版本Taro 1.3的转换,如下图所示:

这个例子不是特别复杂,但却是错误的。

要理解为什么上面的代码报告错误,我们必须首先了解编译阶段。本质上,在编译阶段,代码实际上是一个‘字符串’,在编译阶段的处理方案中,需要从这个‘字符串’中分离出必要的信息(通过AST、正则化等)。)然后做相应的等价转换处理。

对于上面的例子,应该做什么等价的处理?在编译阶段,我们需要分析b是JSX片段:b=a=TextHello/Text,然后将View{b}/View中的{b}等效替换为TextHello/Text。但是在编译阶段很难确定B的值。有人说,回去确定B的值不是不可能,但考虑到b=a,就要先确定A的值,怎么确定A的值呢?需要确定B可以访问的范围链中的A。然而,A可能会被其他变量赋值,它会一圈又一圈地循环。同时,一旦没有简单的赋值,比如函数调用、三元判断等运行时信息,可追溯性就会失效。如果A本身是一个挂在全局对象上的变量,那么追溯性就更不可能了。

因此,在编译阶段不可能简单地确定b的值。

让我们仔细看看上图中的错误信息:a没有定义。

为什么a是未定义的?这是涉及到的另一个问题。我们知道TextHello/Text实际上相当于React.createElement (text,null,‘hello’),以及React的返回值。CreateElement方法是一个普通的JS对象,看起来像

//reactive element对象{tag:text,props:null,children:' hello '.}所以当上述代码实际运行在JS环境中时,大致等价如下:

类App扩展了React。组件{ render () { const a={ tag: Text,props: null,Children:' hello '.} const b=返回{tag:view,props: null,children3360B.}}但是,我们刚才说JSX在编译阶段需要同等对待,JSX需要转换成wxml。因此,JSX片段TextHello/Text得到了特殊处理,a不再是普通的js对象。这里我们看到a变量甚至丢失了,这暴露了一个严重的问题:代码语义被破坏了,也就是说,由于编译时方案对JSX的特殊处理,小程序上实际运行的代码语义并不是你所期望的那样。这令人头痛。

新思维

正是因为编译时方案,比如上面的限制,你经常会有“我还在写React吗?”这种感觉。

这里介绍一个全新的处理思路,在小程序运行过程中几乎和真实的React一样,不会改变任何代码语义。jsX表达式将只作为React.createElement方法的调用进行处理,该方法在实际运行时是一个常见的JS对象,最后以其他方式呈现小程序视图。让我们详细解释一下这个想法的具体内容。

步骤1:给每个独立的JSX片段一个唯一的标识符uuid,假设我们有以下代码:

const A=text uuid=' 000001 ' hello/text const Y=view uuid=' 00002 ' image/text//view我们为A段和Y段添加了uuid属性

第二步:将React代码转义成applet可以通过babel识别的代码,例如,JSX片段被等价的React.createElement等替换。

const a=react . create element(Text,{ uuid: '000001'},' Hello ');步骤3:提取每个独立的JSX片段,用一个小程序模板包装它,并生成wxml文件

模板名称=' 000001 ' TextHello/Text/template template名称=' 000002 ' View uuid=' 000002 ' Image/Text//View/template!-占位符模板-模板为=' {{uid es。名称}} '数据=' { }.uid es } } '/请注意,这里每个模板的名称标识符与JSX片段的唯一标识符uuid相同。最后,需要在最后生成一个占位符模板:模板是=' {{uid es。名称}} '数据=' { }.uid es}}'/。

第四步:修改ReactDOM.render的递归过程(reaction16.x之后就不再递归了),在递归执行阶段,聚合JSX片段的uuid属性,生成并返回uiDes数据结构。

第五步:将第四步生成的uiDes传递给小程序环境,小程序将uiDes设置为占位模板模板为=' {{UIDES。名称}} '数据=' { }.UIDES}}'/,并呈现最终视图。

让我们用上面的App组件的例子来说明整个过程。首先,js代码将被转义如下:

类App扩展了React。组件{ render(){ const a=react . create element(Text,{uuid: '000001'},' Hello ');const b=一个返回(React.createElement(View,{uuid: '000002'},b);)}}同时生成wxml文件:

模板名称=' 000001 ' TextHello/Text/template template名称='000002 '视图模板为='{{child0001.name}} '数据=' { }.child 0001 } } '//视图/模板!-占位符模板-模板是=' {{uid es。名称}} '数据=' { }.uid es } } '/使用我们定制的渲染来执行reaction DOM . render(app/,parent)。在渲染的递归过程中,除了组件实例的例程创建和执行生命周期之外,还会额外收集执行过程中组件的uuid标识符,最终生成uiDes对象

Const uiDes={name:' 00002 ',child00013360 {name3360 000001,} .applet获取此uid es并将其设置为占位符模板模板模板is=' {{uid es。名称}} '数据=' { }.uid es}。最后,呈现小程序视图。

在这整个过程中,你所有的js代码都是在React过程中运行的,语义是一样的,JSX片段也不会特别处理,只是简单的叫React.createElement,另外,因为这里的React过程只是一个纯JS操作,所以执行起来非常迅速,通常只有几ms,最后会输出一个uiDes数据给小程序,小程序会通过这个uiDes渲染视图。

现在我们看一下前面的赋值常量b=a,就不会有问题了,因为a只是一个普通的对象。此外,普通编译时方案的局限性,比如任何函数返回jsX片段,动态生成JSX片段,回收JSX片段,都可以完全解除,因为JSX片段只是JS对象,你可以做任何事情。最后,ReactDOM.render将收集所有执行结果片段的uuid标记并生成uiDes,小程序将根据这个uiDes数据结构呈现最终视图。

可以看出,这种新思想与以前的编译时方案有很大不同。JSX碎片的处理是动态的。你可以在任何地方、任何功能中拥有任何JSX片段。最终的执行结果将决定呈现哪个片段,并且只有执行结果片段的uuid将被写入uiDes。这与编译时方案的静态识别有本质区别。

结论

空谈是廉价的。“给我看看你的代码!”这只是一个想法吗?还是已经完全实现了?

有一个完整的实现。alita项目在处理JSX语法时采用了这种思想,这也是alita能够在不限制其编写的情况下转换整个React Native项目的原因。另外,alita在这个思路上做了很多优化。如果你对这个想法的实现感兴趣,你可以学习alita源代码,这是一个开源的https://github.com/areslabs/alita.

当然,你也可以基于这个思路构建自己的React小程序开发方案。

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

更多资讯
游戏推荐
更多+