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</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
的转换,如下图:
这个例子不是特别复杂,却报错了。
要想理解上面的代码为什么报错,我们首先要理解编译阶段。本质上来说在编译阶段,代码其实就是‘字符串',而编译阶段处理方案,就需要从这个‘字符串'中分析出必要的信息(通过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, 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}}"/>
注意这里每一个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
代码会被转义为:
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}}"/>
。 最终渲染出小程序视图。
在这整个过程中,你的所有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小程序开发方案。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 中国武警男声合唱团《辉煌之声1天路》[DTS-WAV分轨]
- 紫薇《旧曲新韵》[320K/MP3][175.29MB]
- 紫薇《旧曲新韵》[FLAC/分轨][550.18MB]
- 周深《反深代词》[先听版][320K/MP3][72.71MB]
- 李佳薇.2024-会发光的【黑籁音乐】【FLAC分轨】
- 后弦.2012-很有爱【天浩盛世】【WAV+CUE】
- 林俊吉.2012-将你惜命命【美华】【WAV+CUE】
- 晓雅《分享》DTS-WAV
- 黑鸭子2008-飞歌[首版][WAV+CUE]
- 黄乙玲1989-水泼落地难收回[日本天龙版][WAV+CUE]
- 周深《反深代词》[先听版][FLAC/分轨][310.97MB]
- 姜育恒1984《什么时候·串起又散落》台湾复刻版[WAV+CUE][1G]
- 那英《如今》引进版[WAV+CUE][1G]
- 蔡幸娟.1991-真的让我爱你吗【飞碟】【WAV+CUE】
- 群星.2024-好团圆电视剧原声带【TME】【FLAC分轨】