加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 资源网站 > 资源 > 正文

一种让小程序支持JSX语法的新思路

发布时间:2020-12-14 19:17:48 所属栏目:资源 来源:网络整理
导读:React ?社区一直在探寻使用? React ?语法开发小程序的方式,其中比较著名的项目有? Taro ?,? nanachi ?。而使用? React ?语法开发小程序的难点主要就是在? JSX ?语法上,? JSX ?本质上是? JS ?,相比于小程序静态模版来说太灵活。本文所说的新思路就是在处

React?社区一直在探寻使用?React?语法开发小程序的方式,其中比较著名的项目有?Taro?,?nanachi?。而使用?React?语法开发小程序的难点主要就是在?JSX?语法上,?JSX?本质上是?JS?,相比于小程序静态模版来说太灵活。本文所说的新思路就是在处理?JSX?语法上的新思路,这是一种更加动态的处理思路,相比于现有方案,基本上不会限制任何?JSX?的写法,让你以?真正的React方式?处理小程序,希望这个新思路可以给任何有志于用?React?开发小程序的人带来启发。

现有思路的局限

在介绍新的思路之前,我们先来看下?Taro(最新版1.3)?,?nanachi?是怎么在小程序端处理?JSX?语法的。简单来说,主要是通过在?编译阶段?把?JSX?转化为等效的小程序?wxml?来把?React代码运行在小程序端的。

举个例子,比如?React?逻辑表达式:

xx && <Text>Hello</Text>

将会被转化为等效的小程序wx:if指令:

<Text wx:if="{{xx}}">Hello</编译阶段?,他依赖于?编译阶段?的?信息?收集,以上面为例,它必须识别出逻辑表达式,然后做对应的?wx:if?转换处理。

那?编译阶段?有什么问题和局限呢?我们以下面的例子说明:

class App extends React.Component {
    render () {
        const a = <Text>Hello</Text>
        const b = a

        return (
            <View>
                {b}
            </View>
        )
    }
}

首先我们声明?const a = <Text>Hello</Text>?,然后把?a?赋值给了?b?,我们看下最新版本?Taro 1.3?的转换,如下图:

这个例子不是特别复杂,却报错了。

要想理解上面的代码为什么报错,我们首先要理解?编译阶段?。本质上来说在编译阶段,代码其实就是‘字符串’,而?编译阶段?处理方案,就需要从这个‘字符串’中分析出必要的信息(通过?AST,正则等方式)然后做对应的等效转换处理。

而对于上面的例子,需要做什么等效处理呢?需要我们在?编译阶段?分析出?b?是?JSX?片段:?b = a = <Text>Hello</Text>?,然后把?<View>{b}</View>?中的?{b}?等效替换为?<Text>Hello</Text>?。然而在?编译阶段?要想确定?b?的值是很困难的,有人说可以往前追溯来确定b的值,也不是不可以,但是考虑一下 由于?b = a?,那么就先要确定?a?的值,这个?a?的值怎么确定呢?需要在?b?可以访问到的作用域链中确定?a?,然而?a?可能又是由其他变量赋值而来,循环往复,期间一旦出现不是简单赋值的情况,比如函数调用,三元判断等运行时信息,追溯就宣告失败,要是?a?本身就是挂在全局对象上的变量,追溯就更加无从谈起。

所以在?编译阶段?是无法简单确定?b?的值的。

我们再仔细看下上图的报错信息:?a is not defined?。

为什么说?a?未定义呢?这是涉及到另外一个问题,我们知道?<Text>Hello</Text>?,其实等效于?React.createElement(Text,null,'Hello')?,而?React.createElement?方法的返回值就是一个普通?JS?对象,形如

// ReactElement对象
{
   tag: Text,props: null,51); font-weight: 700;">children: 'Hello'
   ...
}

所以上面那一段代码在?JS?环境真正运行的时候,大概等效如下:

o'
            ...
        }
        const b = a

        return {
            tag: View,children: b
            ...
        }
    }
}

但是,我们刚说了编译阶段需要对?JSX?做等效处理,需要把?JSX?转换为?wxml?,所以?<Text>Hello</Text>?这个?JSX?片段被特殊处理了,?a?不再是一个普通?js?对象,这里我们看到?a?变量甚至丢失了,这里暴露了一个很严重的问题:?代码语义被破坏了?,也就是说由于编译时方案对?JSX?的特殊处理,真正运行在小程序上的代码语义并不是你的预期。这个是比较头疼。

新的思路

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

下面我们介绍一种全新的处理思路,这种思路在小程序运行期间和真正的?React?几无区别,不会改变任何代码语义,?JSX?表达式只会被处理为?React.createElement?方法调用,实际运行的时候就是普通?js?对象,最终通过其他方式渲染出小程序视图。下面我们仔细说明一下这个思路的具体内容。

第一步:给每个独立的?JSX?片段打上唯一标识?uuid?,假定我们有如下代码:

const a = <Text uuid="000001">Hello</Text>

const y = <View uuid="000002">
    <Image/>
    <Text/>
</View>

我们给?a?片段,?y?片段 添加了?uuid?属性

第二步:把?React?代码通过?babel?转义为小程序可以识别的代码,例如?JSX?片段用等效的?React.createElement?替换等

const a = React.createElement(Text,{
  uuid: "000001"
},"Hello");

第三步:提取每个独立的?JSX?片段,用小程序?template?包裹,生成?wxml?文件

注意这里每一个?template?的?name?标识和?JSX?片段的唯一标识?uuid?是一样的。最后,需要在结尾生成一个占位模版:?<template is="{{uiDes.name}}" data="{{...uiDes}}"/>?。

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

第五步:把第四步生成的?uiDes?,传递给小程序环境,小程序把?uiDes?设置给占位模版?<template is="{{uiDes.name}}" data="{{...uiDes}}"/>?,渲染出最终的视图。

我们以上面的?App?组件的例子来说明整个过程,首先?js?代码会被转义为:

同时生成?wxml?文件:

使用我们定制之后?render?执行?ReactDOM.render(<App/>,parent)?。在?render?的递归过程中,除了会执行常规的创建组件实例,执行生命周期之外,还会额外的收集执行过程中组件的?uuid?标识,最终生成?uiDes?对象

const uiDes = {
    name: "000002",child0001: {
           name: 000001,...
   }
   
   ...
}

小程序获取到这个?uiDes?,设置给占位模版?<template is="{{uiDes.name}}" data="{{...uiDes}}"/>?。 最终渲染出小程序视图。

在这整个过程中,你的所有?JS?代码都是运行在?React过程?中的,?语义完全一致?,?JSX?片段也不会被任何特殊处理,只是简单的?React.createElement?调用,另外由于这里的?React过程?只是纯?js?运算,执行是非常迅速的,通常只有几ms。最终会输出一个?uiDes?数据到小程序,小程序通过这个?uiDes?渲染出视图。

现在我们在看之前的赋值?const b = a?,就不会有任何问题了,因为?a?不过是普通对象。另外对于常见的?编译时方案的限制?,比如任意函数返回?JSX?片段,动态生成?JSX?片段,?for?循环使用?JSX?片段等等,都可以完全解除了,因为?JSX?片段只是?js?对象,你可以做任何操作,最终?ReactDOM.render?会搜集所有?执行结果的片段?的?uuid?标识,生成?uiDes?,而小程序会根据这个?uiDes?数据结构渲染出最终视图。

可以看出这种新的思路和以前?编译时?方案还是有很大的区别的,对?JSX?片段的处理是动态的,你可以在任何地方,任何函数出现任何?JSX?片段,最终执行结果会确定渲染哪一个片段,只有执行结果的片段的?uuid?会被写入?uiDes?。这和?编译时?方案的静态识别有着本质的区别。

结语

"Talk is cheap. Show me your code!" 这仅仅是一个思路?还是已经有落地完整的实现呢?

是有完整的实现的,?alita?项目在处理?JSX?语法的时候,采用的就是这个思路,这也是?alita?基本不限制写法却可以转化整个React Native项目的原因,另外?alita?在这个思路上做了很多优化。如果对这个思路的具体实现有兴趣,可以去研读一下?alita?源码,它完全是开源的?https://github.com/areslabs/alita?。

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

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读