溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

淺談一種讓小程序支持JSX語(yǔ)法的新思路

發(fā)布時(shí)間:2020-09-29 11:25:44 來(lái)源:腳本之家 閱讀:125 作者:ykforerlang 欄目:web開(kāi)發(fā)

React社區(qū)一直在探尋使用React語(yǔ)法開(kāi)發(fā)小程序的方式,其中比較著名的項(xiàng)目有Taronanachi。而使用React語(yǔ)法開(kāi)發(fā)小程序的難點(diǎn)主要就是在JSX語(yǔ)法上,JSX本質(zhì)上是JS,相比于小程序靜態(tài)模版來(lái)說(shuō)太靈活。本文所說(shuō)的新思路就是在處理JSX語(yǔ)法上的新思路,這是一種更加動(dòng)態(tài)的處理思路,相比于現(xiàn)有方案,基本上不會(huì)限制任何JSX的寫法,讓你以真正的React方式處理小程序,希望這個(gè)新思路可以給任何有志于用React開(kāi)發(fā)小程序的人帶來(lái)啟發(fā)。

現(xiàn)有思路的局限

在介紹新的思路之前,我們先來(lái)看下Taro(最新版1.3),nanachi是怎么在小程序端處理JSX語(yǔ)法的。簡(jiǎn)單來(lái)說(shuō),主要是通過(guò)在編譯階段把JSX轉(zhuǎn)化為等效的小程序wxml來(lái)把React代碼運(yùn)行在小程序端的。

舉個(gè)例子,比如React邏輯表達(dá)式:

xx && <Text>Hello</Text>

將會(huì)被轉(zhuǎn)化為等效的小程序wx:if指令:

<Text wx:if="{{xx}}">Hello</Text>

這種方式把對(duì)JSX的處理,主要放在了編譯階段,他依賴于編譯階段的信息收集,以上面為例,它必須識(shí)別出邏輯表達(dá)式,然后做對(duì)應(yīng)的wx:if轉(zhuǎn)換處理。

那編譯階段有什么問(wèn)題和局限呢?我們以下面的例子說(shuō)明:

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)換,如下圖:

淺談一種讓小程序支持JSX語(yǔ)法的新思路

這個(gè)例子不是特別復(fù)雜,卻報(bào)錯(cuò)了。

要想理解上面的代碼為什么報(bào)錯(cuò),我們首先要理解編譯階段。本質(zhì)上來(lái)說(shuō)在編譯階段,代碼其實(shí)就是‘字符串',而編譯階段處理方案,就需要從這個(gè)‘字符串'中分析出必要的信息(通過(guò)AST,正則等方式)然后做對(duì)應(yīng)的等效轉(zhuǎn)換處理。

而對(duì)于上面的例子,需要做什么等效處理呢?需要我們?cè)诰幾g階段分析出bJSX片段:b = a = <Text>Hello</Text>,然后把<View></View>中的等效替換為<Text>Hello</Text>。然而在編譯階段要想確定b的值是很困難的,有人說(shuō)可以往前追溯來(lái)確定b的值,也不是不可以,但是考慮一下 由于b = a,那么就先要確定a的值,這個(gè)a的值怎么確定呢?需要在b可以訪問(wèn)到的作用域鏈中確定a,然而a可能又是由其他變量賦值而來(lái),循環(huán)往復(fù),期間一旦出現(xiàn)不是簡(jiǎn)單賦值的情況,比如函數(shù)調(diào)用,三元判斷等運(yùn)行時(shí)信息,追溯就宣告失敗,要是a本身就是掛在全局對(duì)象上的變量,追溯就更加無(wú)從談起。

所以在編譯階段 是無(wú)法簡(jiǎn)單確定b的值的。

我們?cè)僮屑?xì)看下上圖的報(bào)錯(cuò)信息:a is not defined。

淺談一種讓小程序支持JSX語(yǔ)法的新思路

為什么說(shuō)a未定義呢?這是涉及到另外一個(gè)問(wèn)題,我們知道<Text>Hello</Text>,其實(shí)等效于React.createElement(Text, null, 'Hello'),而React.createElement方法的返回值就是一個(gè)普通JS對(duì)象,形如

// ReactElement對(duì)象
{
  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
      ...
    }
  }
}

但是,我們剛說(shuō)了編譯階段需要對(duì)JSX做等效處理,需要把JSX轉(zhuǎn)換為wxml,所以<Text>Hello</Text>這個(gè)JSX片段被特殊處理了,a不再是一個(gè)普通js對(duì)象,這里我們看到a變量甚至丟失了,這里暴露了一個(gè)很嚴(yán)重的問(wèn)題:代碼語(yǔ)義被破壞了,也就是說(shuō)由于編譯時(shí)方案對(duì)JSX的特殊處理,真正運(yùn)行在小程序上的代碼語(yǔ)義并不是你的預(yù)期。這個(gè)是比較頭疼。

新的思路

正因?yàn)榫幾g時(shí)方案,有如上的限制,在使用的時(shí)候常常讓你有“我還是在寫React嗎?”這種感覺(jué)。

下面我們介紹一種全新的處理思路,這種思路在小程序運(yùn)行期間和真正的React幾無(wú)區(qū)別,不會(huì)改變?nèi)魏未a語(yǔ)義,JSX表達(dá)式只會(huì)被處理為React.createElement方法調(diào)用,實(shí)際運(yùn)行的時(shí)候就是普通js對(duì)象,最終通過(guò)其他方式渲染出小程序視圖。下面我們仔細(xì)說(shuō)明一下這個(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代碼通過(guò)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è)templatename標(biāo)識(shí)和 JSX片段的唯一標(biāo)識(shí)uuid是一樣的。最后,需要在結(jié)尾生成一個(gè)占位模版:<template is="{{uiDes.name}}" data="{{...uiDes}}"/>。

第四步:修改ReactDOM.render的遞歸(React 16.x之后,不在是遞歸的方式)過(guò)程,遞歸執(zhí)行階段,聚合JSX片段的uuid屬性,生成并返回uiDes數(shù)據(jù)結(jié)構(gòu)。

第五步:把第四步生成的uiDes,傳遞給小程序環(huán)境,小程序把uiDes 設(shè)置給占位模版<template is="{{uiDes.name}}" data="{{...uiDes}}"/>,渲染出最終的視圖。

我們以上面的App組件的例子來(lái)說(shuō)明整個(gè)過(guò)程,首先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的遞歸過(guò)程中,除了會(huì)執(zhí)行常規(guī)的創(chuàng)建組件實(shí)例,執(zhí)行生命周期之外,還會(huì)額外的收集執(zhí)行過(guò)程中組件的uuid標(biāo)識(shí),最終生成 uiDes 對(duì)象

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

小程序獲取到這個(gè)uiDes,設(shè)置給占位模版<template is="{{uiDes.name}}" data="{{...uiDes}}"/>。 最終渲染出小程序視圖。

淺談一種讓小程序支持JSX語(yǔ)法的新思路

在這整個(gè)過(guò)程中,你的所有JS代碼都是運(yùn)行在React過(guò)程中的,語(yǔ)義完全一致,JSX片段也不會(huì)被任何特殊處理,只是簡(jiǎn)單的React.createElement調(diào)用,另外由于這里的React過(guò)程只是純js運(yùn)算,執(zhí)行是非常迅速的,通常只有幾ms。最終會(huì)輸出一個(gè)uiDes數(shù)據(jù)到小程序,小程序通過(guò)這個(gè)uiDes渲染出視圖。

現(xiàn)在我們?cè)诳粗暗馁x值const b = a,就不會(huì)有任何問(wèn)題了,因?yàn)?code>a 不過(guò)是普通對(duì)象。另外對(duì)于常見(jiàn)的編譯時(shí)方案的限制,比如任意函數(shù)返回JSX片段,動(dòng)態(tài)生成JSX片段,for循環(huán)使用JSX片段等等,都可以完全解除了,因?yàn)?code>JSX片段只是js對(duì)象,你可以做任何操作,最終ReactDOM.render會(huì)搜集所有執(zhí)行結(jié)果的片段的uuid標(biāo)識(shí),生成uiDes,而小程序會(huì)根據(jù)這個(gè)uiDes數(shù)據(jù)結(jié)構(gòu)渲染出最終視圖。

可以看出這種新的思路和以前編譯時(shí)方案還是有很大的區(qū)別的,對(duì)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é)語(yǔ)

"Talk is cheap. Show me your code!" 這僅僅是一個(gè)思路?還是已經(jīng)有落地完整的實(shí)現(xiàn)呢?

是有完整的實(shí)現(xiàn)的,alita項(xiàng)目在處理JSX語(yǔ)法的時(shí)候,采用的就是這個(gè)思路,這也是alita基本不限制寫法卻可以轉(zhuǎn)化整個(gè)React Native項(xiàng)目的原因,另外alita在這個(gè)思路上做了很多優(yōu)化。如果對(duì)這個(gè)思路的具體實(shí)現(xiàn)有興趣,可以去研讀一下alita源碼,它完全是開(kāi)源的https://github.com/areslabs/alita。

當(dāng)然,你也可以基于這個(gè)思路,構(gòu)造出自己的React小程序開(kāi)發(fā)方案。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI