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


Posted in Javascript onJune 16, 2019

React社区一直在探寻使用React语法开发小程序的方式,其中比较著名的项目有Taronanachi。而使用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</Text>

这种方式把对JSX的处理,主要放在了编译阶段,他依赖于编译阶段的信息收集,以上面为例,它必须识别出逻辑表达式,然后做对应的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的转换,如下图:

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

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

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

而对于上面的例子,需要做什么等效处理呢?需要我们在编译阶段分析出bJSX片段: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

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

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

// ReactElement对象
{
  tag: Text,
  props: null,
  children: 'Hello'
  ...
}

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

class App extends React.Component {
  render () {
    const a = {
      tag: Text,
      props: null,
      children: 'Hello'
      ...
    }
    const b = a

    return {
      tag: View,
      props: null,
      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="000001">
  <Text>Hello</Text>
</template>

<template name="000002">
  <View uuid="000002">
    <Image/>
    <Text/>
  </View>
</template>


<!--占位template-->
<template is="{{uiDes.name}}" data="{{...uiDes}}"/>

注意这里每一个templatename标识和 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代码会被转义为:

class App extends React.Component {
  render () {
    const a = React.createElement(Text, {uuid: "000001"}, "Hello");
    const b = a
    
    return (
     React.createElement(View, {uuid: "000002"} , b);
    )
   }
}

同时生成wxml文件:

<template name="000001">
  <Text>Hello</Text>
</template>

<template name="000002">
  <View>
    <template is="{{child0001.name}}" data="{{...child0001}}"/>
  </View>
</template>

<!--占位template-->
<template is="{{uiDes.name}}" data="{{...uiDes}}"/>

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

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

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

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

在这整个过程中,你的所有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小程序开发方案。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
自动检查并替换文本框内的字符
Jun 30 Javascript
二级域名或跨域共享Cookies的实现方法
Aug 07 Javascript
formValidator3.3的ajaxValidator一些异常分析
Jul 12 Javascript
jQuery中extend函数详解
Jul 13 Javascript
JavaScript闭包实例详解
Jun 03 Javascript
微信小程序 框架详解及实例应用
Sep 26 Javascript
详解jQuery简单的表格应用
Dec 16 Javascript
jQuery Password Validation密码验证
Dec 30 Javascript
关于angular js_$watch监控属性和对象详解
Apr 24 Javascript
三种Webpack打包方式(小结)
Sep 19 Javascript
vue模仿网易云音乐的单页面应用
Apr 24 Javascript
vue使用高德地图点击下钻上浮效果的实现思路
Oct 12 Javascript
JavaScript 处理树数据结构的方法示例
Jun 16 #Javascript
JavaScript中的ES6 Proxy的具体使用
Jun 16 #Javascript
简谈创建React Component的几种方式
Jun 15 #Javascript
JS中的一些常用的函数式编程术语
Jun 15 #Javascript
JavaScript模块管理的简单实现方式详解
Jun 15 #Javascript
JavaScript工具库之Lodash详解
Jun 15 #Javascript
jQuery创建折叠式菜单
Jun 15 #jQuery
You might like
PHP文件打开、关闭、写入的判断与执行代码
2011/05/24 PHP
PHP实现的下载远程图片自定义函数分享
2015/01/28 PHP
php利用smtp类实现电子邮件发送
2015/10/30 PHP
thinkPHP3.x常量整理(预定义常量/路径常量/系统常量)
2016/05/20 PHP
py文件转exe时包含paramiko模块出错解决方法
2016/08/12 PHP
PHP实现求解最长公共子串问题的方法
2017/11/17 PHP
js判断字符长度以及中英文数字等
2013/12/31 Javascript
jQuery实现的兼容性浮动层示例
2016/08/02 Javascript
JavaScript日期选择功能示例
2017/01/16 Javascript
javaScript之split与join的区别(详解)
2017/11/08 Javascript
微信小程序调用摄像头隐藏式拍照功能
2018/08/22 Javascript
AngularJs1.x自定义指令独立作用域的函数传入参数方法
2018/10/09 Javascript
PHP实现基于Redis的MessageQueue队列封装操作示例
2019/02/02 Javascript
vue+elementUI动态生成面包屑导航教程
2019/11/04 Javascript
详解JSON.stringify()的5个秘密特性
2020/05/26 Javascript
Kettle中使用JavaScrip调用jar包对文件内容进行MD5加密的操作方法
2020/09/04 Javascript
解决vue项目中遇到 Cannot find module ‘chalk‘ 报错的问题
2020/11/05 Javascript
[01:57]DOTA2上海特锦赛小组赛解说单车采访花絮
2016/02/27 DOTA
python设置windows桌面壁纸的实现代码
2013/01/28 Python
跟老齐学Python之玩转字符串(3)
2014/09/14 Python
Python实现网站文件的全备份和差异备份
2014/11/30 Python
Python简单生成随机姓名的方法示例
2017/12/27 Python
Python模块搜索路径代码详解
2018/01/29 Python
使用python 写一个静态服务(实战)
2019/06/28 Python
Python利用matplotlib绘制约数个数统计图示例
2019/11/26 Python
Python关于__name__属性的含义和作用详解
2020/02/19 Python
Python TestSuite生成测试报告过程解析
2020/07/23 Python
将HTML5 Canvas的内容保存为图片借助toDataURL实现
2013/05/20 HTML / CSS
如何写出高性能的JSP和Servlet
2013/01/22 面试题
司法局火灾防控方案
2014/06/05 职场文书
海洋科学专业求职信
2014/08/10 职场文书
家庭教育的心得体会
2014/09/01 职场文书
投标承诺函范文
2015/01/21 职场文书
实习指导老师意见
2015/06/04 职场文书
大学体育课感想
2015/08/10 职场文书
MySQL的Query Cache图文详解
2021/07/01 MySQL