淺談一種讓小程序支持JSX語法的新思路
React
社區(qū)一直在探尋使用React
語法開發(fā)小程序的方式,其中比較著名的項(xiàng)目有Taro
,nanachi
。而使用React
語法開發(fā)小程序的難點(diǎn)主要就是在JSX
語法上,JSX
本質(zhì)上是JS
,相比于小程序靜態(tài)模版來說太靈活。本文所說的新思路就是在處理JSX
語法上的新思路,這是一種更加動(dòng)態(tài)的處理思路,相比于現(xiàn)有方案,基本上不會(huì)限制任何JSX
的寫法,讓你以真正的React方式處理小程序,希望這個(gè)新思路可以給任何有志于用React
開發(fā)小程序的人帶來啟發(fā)。
現(xiàn)有思路的局限
在介紹新的思路之前,我們先來看下Taro(最新版1.3)
,nanachi
是怎么在小程序端處理JSX
語法的。簡單來說,主要是通過在編譯階段把JSX
轉(zhuǎn)化為等效的小程序wxml
來把React
代碼運(yùn)行在小程序端的。
舉個(gè)例子,比如React
邏輯表達(dá)式:
xx && <Text>Hello</Text>
將會(huì)被轉(zhuǎn)化為等效的小程序wx:if指令:
<Text wx:if="{{xx}}">Hello</Text>
這種方式把對JSX
的處理,主要放在了編譯階段,他依賴于編譯階段的信息收集,以上面為例,它必須識(shí)別出邏輯表達(dá)式,然后做對應(yīng)的wx:if
轉(zhuǎn)換處理。
那編譯階段有什么問題和局限呢?我們以下面的例子說明:
class App extends React.Component { render () { const a = <Text>Hello</Text> const b = a return ( <View> </View> ) } }
首先我們聲明 const a = <Text>Hello</Text>
,然后把a
賦值給了b
,我們看下最新版本Taro 1.3
的轉(zhuǎn)換,如下圖:
這個(gè)例子不是特別復(fù)雜,卻報(bào)錯(cuò)了。
要想理解上面的代碼為什么報(bào)錯(cuò),我們首先要理解編譯階段。本質(zhì)上來說在編譯階段,代碼其實(shí)就是‘字符串',而編譯階段處理方案,就需要從這個(gè)‘字符串'中分析出必要的信息(通過AST
,正則等方式)然后做對應(yīng)的等效轉(zhuǎn)換處理。
而對于上面的例子,需要做什么等效處理呢?需要我們在編譯階段分析出b
是JSX
片段:b = a = <Text>Hello</Text>
,然后把<View></View>
中的等效替換為
<Text>Hello</Text>
。然而在編譯階段要想確定b
的值是很困難的,有人說可以往前追溯來確定b的值,也不是不可以,但是考慮一下 由于b = a
,那么就先要確定a
的值,這個(gè)a
的值怎么確定呢?需要在b
可以訪問到的作用域鏈中確定a
,然而a
可能又是由其他變量賦值而來,循環(huán)往復(fù),期間一旦出現(xiàn)不是簡單賦值的情況,比如函數(shù)調(diào)用,三元判斷等運(yùn)行時(shí)信息,追溯就宣告失敗,要是a
本身就是掛在全局對象上的變量,追溯就更加無從談起。
所以在編譯階段 是無法簡單確定b
的值的。
我們再仔細(xì)看下上圖的報(bào)錯(cuò)信息:a is not defined
。
為什么說a
未定義呢?這是涉及到另外一個(gè)問題,我們知道<Text>Hello</Text>
,其實(shí)等效于React.createElement(Text, null, 'Hello')
,而React.createElement
方法的返回值就是一個(gè)普通JS
對象,形如
// ReactElement對象 { tag: Text, props: null, children: 'Hello' ... }
所以上面那一段代碼在JS
環(huán)境真正運(yùn)行的時(shí)候,大概等效如下:
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
轉(zhuǎn)換為wxml
,所以<Text>Hello</Text>
這個(gè)JSX
片段被特殊處理了,a
不再是一個(gè)普通js
對象,這里我們看到a
變量甚至丟失了,這里暴露了一個(gè)很嚴(yán)重的問題:代碼語義被破壞了,也就是說由于編譯時(shí)方案對JSX
的特殊處理,真正運(yùn)行在小程序上的代碼語義并不是你的預(yù)期。這個(gè)是比較頭疼。
新的思路
正因?yàn)榫幾g時(shí)方案,有如上的限制,在使用的時(shí)候常常讓你有“我還是在寫React
嗎?”這種感覺。
下面我們介紹一種全新的處理思路,這種思路在小程序運(yùn)行期間和真正的React
幾無區(qū)別,不會(huì)改變?nèi)魏未a語義,JSX
表達(dá)式只會(huì)被處理為React.createElement
方法調(diào)用,實(shí)際運(yùn)行的時(shí)候就是普通js
對象,最終通過其他方式渲染出小程序視圖。下面我們仔細(xì)說明一下這個(gè)思路的具體內(nèi)容。
第一步:給每個(gè)獨(dú)立的JSX
片段打上唯一標(biāo)識(shí)uuid
,假定我們有如下代碼:
const a = <Text uuid="000001">Hello</Text> const y = <View uuid="000002"> <Image/> <Text/> </View>
我們給a
片段,y
片段 添加了uuid
屬性
第二步:把React
代碼通過babel
轉(zhuǎn)義為小程序可以識(shí)別的代碼,例如JSX
片段用等效的React.createElement
替換等
const a = React.createElement(Text, { uuid: "000001" }, "Hello");
第三步:提取每個(gè)獨(dú)立的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}}"/>
注意這里每一個(gè)template
的name
標(biāo)識(shí)和 JSX
片段的唯一標(biāo)識(shí)uuid
是一樣的。最后,需要在結(jié)尾生成一個(gè)占位模版:<template is="{{uiDes.name}}" data="{{...uiDes}}"/>
。
第四步:修改ReactDOM.render
的遞歸(React 16.x
之后,不在是遞歸的方式)過程,遞歸執(zhí)行階段,聚合JSX
片段的uuid
屬性,生成并返回uiDes
數(shù)據(jù)結(jié)構(gòu)。
第五步:把第四步生成的uiDes
,傳遞給小程序環(huán)境,小程序把uiDes
設(shè)置給占位模版<template is="{{uiDes.name}}" data="{{...uiDes}}"/>
,渲染出最終的視圖。
我們以上面的App
組件的例子來說明整個(gè)過程,首先js
代碼會(huì)被轉(zhuǎn)義為:
class App extends React.Component { render () { const a = React.createElement(Text, {uuid: "000001"}, "Hello"); const b = a return ( React.createElement(View, {uuid: "000002"} , b); ) } }
同時(shí)生成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
執(zhí)行ReactDOM.render(<App/>, parent)
。在render
的遞歸過程中,除了會(huì)執(zhí)行常規(guī)的創(chuàng)建組件實(shí)例,執(zhí)行生命周期之外,還會(huì)額外的收集執(zhí)行過程中組件的uuid
標(biāo)識(shí),最終生成 uiDes
對象
const uiDes = { name: "000002", child0001: { name: 000001, ... } ... }
小程序獲取到這個(gè)uiDes
,設(shè)置給占位模版<template is="{{uiDes.name}}" data="{{...uiDes}}"/>
。 最終渲染出小程序視圖。
在這整個(gè)過程中,你的所有JS
代碼都是運(yùn)行在React過程
中的,語義完全一致,JSX
片段也不會(huì)被任何特殊處理,只是簡單的React.createElement
調(diào)用,另外由于這里的React過程
只是純js
運(yùn)算,執(zhí)行是非常迅速的,通常只有幾ms。最終會(huì)輸出一個(gè)uiDes
數(shù)據(jù)到小程序,小程序通過這個(gè)uiDes
渲染出視圖。
現(xiàn)在我們在看之前的賦值const b = a
,就不會(huì)有任何問題了,因?yàn)?code>a 不過是普通對象。另外對于常見的編譯時(shí)方案的限制,比如任意函數(shù)返回JSX
片段,動(dòng)態(tài)生成JSX
片段,for
循環(huán)使用JSX
片段等等,都可以完全解除了,因?yàn)?code>JSX片段只是js
對象,你可以做任何操作,最終ReactDOM.render
會(huì)搜集所有執(zhí)行結(jié)果的片段的uuid
標(biāo)識(shí),生成uiDes
,而小程序會(huì)根據(jù)這個(gè)uiDes
數(shù)據(jù)結(jié)構(gòu)渲染出最終視圖。
可以看出這種新的思路和以前編譯時(shí)方案還是有很大的區(qū)別的,對JSX
片段的處理是動(dòng)態(tài)的,你可以在任何地方,任何函數(shù)出現(xiàn)任何JSX
片段, 最終執(zhí)行結(jié)果會(huì)確定渲染哪一個(gè)片段,只有執(zhí)行結(jié)果的片段的uuid
會(huì)被寫入uiDes
。這和編譯時(shí)方案的靜態(tài)識(shí)別有著本質(zhì)的區(qū)別。
結(jié)語
"Talk is cheap. Show me your code!" 這僅僅是一個(gè)思路?還是已經(jīng)有落地完整的實(shí)現(xiàn)呢?
是有完整的實(shí)現(xiàn)的,alita項(xiàng)目在處理JSX
語法的時(shí)候,采用的就是這個(gè)思路,這也是alita基本不限制寫法卻可以轉(zhuǎn)化整個(gè)React Native項(xiàng)目的原因,另外alita在這個(gè)思路上做了很多優(yōu)化。如果對這個(gè)思路的具體實(shí)現(xiàn)有興趣,可以去研讀一下alita源碼,它完全是開源的https://github.com/areslabs/alita。
當(dāng)然,你也可以基于這個(gè)思路,構(gòu)造出自己的React小程序開發(fā)方案。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于JavaScript實(shí)現(xiàn)帶數(shù)據(jù)驗(yàn)證和復(fù)選框的表單提交
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)帶數(shù)據(jù)驗(yàn)證和復(fù)選框的表單提交功能,需要的朋友可以參考下2017-08-08javascript實(shí)現(xiàn)數(shù)字倒計(jì)時(shí)特效
這篇文章主要介紹了javascript實(shí)現(xiàn)網(wǎng)頁倒計(jì)時(shí)數(shù)字時(shí)鐘效果,是一款非常實(shí)用的javascript倒計(jì)時(shí)特效,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-03-03基于OO的動(dòng)畫附加插件,可以實(shí)現(xiàn)彈跳、漸隱等動(dòng)畫效果 分享
基于OO的動(dòng)畫附加插件,可以實(shí)現(xiàn)彈跳、漸隱等動(dòng)畫效果 分享,需要的朋友可以參考一下2013-06-06js實(shí)現(xiàn)input的賦值小結(jié)
這篇文章主要介紹了js實(shí)現(xiàn)input的賦值問題小結(jié),在實(shí)際的開發(fā)中,為了頁面的美觀,可能用到一些框架,比如EasyUI框架,文中介紹了easyui的input框賦值代碼,感興趣的朋友一起看看吧2023-12-12js正則校驗(yàn)特殊的不可見字符的具體實(shí)現(xiàn)
用戶可能從Excel或者其他地方直接復(fù)制粘貼,這時(shí)候提交到后端會(huì)導(dǎo)致獲取的用戶輸入中包含一些特殊的不可見字符,本文主要介紹了js正則校驗(yàn)特殊的不可見字符的具體實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06uniapp微信小程序獲取當(dāng)前定位城市信息的實(shí)例代碼
因?yàn)閡niapp官網(wǎng)文檔的定位功能,只能提供經(jīng)緯度,如果要獲取當(dāng)前具體的位置信息,必須要調(diào)取官方的地圖方法,然后在地圖上選點(diǎn),下面這篇文章主要給大家介紹了關(guān)于uniapp微信小程序獲取當(dāng)前定位城市信息的相關(guān)資料,需要的朋友可以參考下2022-08-08