Taro跨三端平臺的應(yīng)用與實踐

背景

Taro 是一套遵循 React 語法規(guī)范的 多端開發(fā) 解決方案∽現(xiàn)如今市面上端的形態(tài)多種多樣,Web、React-Native囊骤、微信小程序等各種端大行其道晃择,當(dāng)業(yè)務(wù)同時在不同的端都要求有所表現(xiàn)的時候,針對不同的端編寫多套代碼的成本顯然非常高也物,這時只編寫一套代碼就能適配到多端的能力就顯得極為重要宫屠。
使用 Taro,我們只需書寫一套代碼滑蚯,再通過 Taro 的編譯工具浪蹂,即可將源代碼分別編譯出在不同端(微信/百度/支付寶/字節(jié)跳動小程序、快應(yīng)用告材、H5坤次、React-Native 等)運行的代碼。
58租房-創(chuàng)新找房業(yè)務(wù)有上線三端并高度一致的需求斥赋,為了節(jié)約開發(fā)人力和創(chuàng)新缰猴,我們在該項目中探索性的嘗試使用Taro進(jìn)行開發(fā),下面將從 Taro框架編譯原理灿渴、源碼改造洛波、依賴改造、業(yè)務(wù)開發(fā)實踐過程幾個角度來分別闡述一下骚露。

Taro框架編譯原理

因為H5和React-Native都可以遵循React語法規(guī)范開發(fā)蹬挤,在 React 中,是使用 JSX 來作為組件的模板的棘幸,而小程序則與 Vue 一樣焰扳,是使用字符串模板的。這樣兩者之間就有著巨大的差異了误续。
JSX

render () {
  return (
    <View className='index'>
      {this.state.list.map((item, idx) => (
        <View key={idx}>{item}</View>
      ))}
      <Button onClick={this.jump}>跳轉(zhuǎn)</Button>
    </View>
  )
}

小程序模板

<view class="index">
  <view wx:key={idx} wx:for="{{list}}" wx:for-item="item" wx:for-index="idx">{{item}}</view>
  <view bindtap="jump">跳轉(zhuǎn)</view>
</view>

那么這個時候我們就想吨悍,要是能夠?qū)?JSX 編譯成小程序模板就好了。

事實上在我們平時的開發(fā)中蹋嵌,這種編譯的操作到處可見育瓜,babel 就是我們最常用的 JS 代碼編譯器,一般瀏覽器是不能支持一些非常新的語法特性的栽烂,但我們又想使用它們躏仇,這個時候就可以借助 babel 來將我們的高版本的 ES 代碼,編譯成瀏覽器可以運行的 ES 代碼腺办。而我們像要將 JSX編譯成小程序模板焰手,也是同樣的道理。我們首先來了解一下 Babel 的運行機(jī)制怀喉。

Babel 作為一個 代碼編譯器 书妻,能夠?qū)?ES6/7/8 的代碼編譯成 ES5 的代碼,其核心利用的就是計算中非彻#基礎(chǔ)的編譯原理知識躲履,將輸入語言代碼见间,通過編譯器執(zhí)行,輸出目標(biāo)語言的代碼崇呵。編譯原理的一般過程就是缤剧,輸入源程序,經(jīng)過詞法分析域慷、語法分析荒辕,構(gòu)造出語法樹,再經(jīng)過語義分析犹褒,理解程序正確與否抵窒,再對語法樹做出需要的操作與優(yōu)化,最終生成目標(biāo)代碼叠骑。

image.png

將 JSX 編譯成小程序模板李皇,非常幸運的是 babel 的核心編譯器 babylon 是支持對 JSX 語法的解析的,我們可以直接利用它來幫我們構(gòu)造 AST宙枷,而我們需要專注的核心就是如何對 AST 進(jìn)行轉(zhuǎn)換操作掉房,得出我們需要的新 AST,再將新 AST 進(jìn)行遞歸遍歷慰丛,生成小程序的模板卓囚。

源碼改造

我們是基于Taro1.3.10版本開發(fā)的,為了使Taro兼容58RN工程我們對源碼做了以下改造:
taro腳手架taro-cli

  1. 升級內(nèi)部react-native版本"react-native": "0.57.8"
  2. 固定注冊包名AppRegistry.registerComponentwuba

taro依賴庫taro-rn
1.刪除Expo相關(guān)依賴的api诅病,保證依賴純凈

taro依賴庫taro-router-rn
修改initRouter中基于react-navigation的初始化參數(shù)哪亿,使其保證和58RN跳轉(zhuǎn)方式統(tǒng)一

為了便于版本管理,我們將taro關(guān)鍵字轉(zhuǎn)換為fangchan-taro贤笆,并修改其依賴樹蝇棉,fangchan-taro托管于npm,后續(xù)陸續(xù)改造了taro-cli、taro-rn、taro-rn-component等依賴卓箫,我們把Taro下的依賴統(tǒng)一進(jìn)行管理,置于同一個組織贴唇,標(biāo)記為公開,形如:
@fangchan/taro-rn飞袋、@fangchan/taro-cli

image.png

圖上是Taro開發(fā)RN端的工作流程,通過執(zhí)行taro build --type rn --watch指令將Taro編譯成RN代碼链患,并開啟metro server將rn_temp下的js文件打包成js bundle巧鸭,通過npm start開啟服務(wù)后就可以在58rn測試載體頁上愉快的訪問了。

依賴改造

Taro提供的組件和Api相對比較基礎(chǔ)麻捻,通用性更強(qiáng)纲仍,但是58移動端并沒有對應(yīng)的sdk支持呀袱,所以我們暫時去掉了不支持的api,并在有必要的情況下進(jìn)行重寫郑叠。
例如taro-rn中的請求Request是必要的API夜赵,我們對其改造,讓它內(nèi)部調(diào)用房產(chǎn)SDK的請求乡革,保證請求內(nèi)部流程一致寇僧。

import HMS from 'house-middleware-sdk'
function request (options) {
  options = options || {}
  let url = options.url
  let data = options.data || {}
  if (typeof options === 'string') {
    options = {
      url: options
    }
  }
  let method = options.method || 'GET'
  method = method.toUpperCase()
  if (method === 'GET') {
    return HMS.get(url, data)
  }
  if (method === 'POST') {
    const formData = new FormData()
    Object.keys(data).forEach(key => {
      formData.append(key, data[key])
    })
    return HMS.post(url, formData)
  }
}
export default {
  request
}

房產(chǎn)業(yè)務(wù)中使用到的業(yè)務(wù)組件如篩選、底部欄等沸版,不同端提供的Api如登錄嘁傀、認(rèn)證等需要我們自行開發(fā),我們把開發(fā)分為兩個階段:

  • 第一階段為了業(yè)務(wù)快速遷移上線视粮,盡量復(fù)用各端已有組件细办,使用條件編譯完成各端打包
  • 第二階段對于通用性較強(qiáng)的組件、基于Taro封裝各端API和組件庫提高開發(fā)效率蕾殴、降低維護(hù)成本
    目前我們處于第一階段笑撞,使用Taro提供的條件編譯來處理不同端的差異
if (process.env.TARO_ENV === 'weapp') {
    // 微信小程序端執(zhí)行邏輯
} else if (process.env.TARO_ENV === 'h5') {
    // h5 端執(zhí)行邏輯
} else if (process.env.TARO_ENV === 'rn') {
    // react-native 端執(zhí)行邏輯
}

業(yè)務(wù)開發(fā)實現(xiàn)

1.項目初始化

我們通過改造的cli來快速構(gòu)建項目fangchan-taro init TaroDemo,構(gòu)建好后項目目錄結(jié)構(gòu)如下

├── dist                   編譯結(jié)果目錄
├── config                 配置目錄
|   ├── dev.js             開發(fā)時配置
|   ├── index.js           默認(rèn)配置
|   └── prod.js            打包時配置
├── src                    源碼目錄
|   ├── pages              頁面文件目錄
|   |   ├── index          index 頁面目錄
|   |   |   ├── index.js   index 頁面邏輯
|   |   |   └── index.css  index 頁面樣式
|   ├── app.css            項目總通用樣式
|   └── app.js             項目入口文件
└── package.json

我們首先需要修改入口文件钓觉,在構(gòu)造中初始化全局參數(shù)茴肥,在全局配置config中定義頁面以及設(shè)置小程序?qū)Ш綑诤驮O(shè)置rn特殊返回鍵處理等操作。

class App extends Component {

  constructor(props) {
    super(props);
    initGlobalConst();
  }

  config = {
    pages: [
      'pages/index/index',
      'pages/listpage/index',
      'pages/filterpage/index',
      'pages/guidepage/index',
      'pages/demandpage/index',
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#fff',
      navigationBarTitleText: 'WeChat',
      navigationBarTextStyle: 'black',
    },
    backStackConfig: {
      ctrlFun: process.env.TARO_ENV === 'rn' ? 
      require('./utils/initRN').ctrlBackStackFun : false
    }
  };

  render() {
    return (
      <Index/>
    )
  }
}

Taro.render(<App/>, document.getElementById('app'));

然后在'src/index/index.js'中的componentWillMount生命周期處理多端的初始化操作

componentWillMount() {
    process.env.TARO_ENV === 'rn' && initRN((goal) => {
      this.renderPage();
    });
    process.env.TARO_ENV === 'h5' && initH5((goal) => {
      this.renderPage();
    });
    process.env.TARO_ENV === 'weapp' && initH5((goal) => {
      this.renderPage();
    });
  }

以RN端為例议谷,需要在initRN方法中從native端獲取header信息炉爆、跳轉(zhuǎn)協(xié)議等,所以這一步是必不可少的卧晓。

function initRN(callback) {
  HMS.initPackage(
    () => HMS.initNativeParams(
      () => {
        const cacheFlag = global.jumpParams.content.params.useCache || true;
        HMS.CacheUtil.setUseCache(cacheFlag);
        upDateGlobalConst('PACKAGE', global.CURRENT_PACKAGE);
        upDateGlobalConst('FULL_PATH', global.jumpParams.content.params.full_path);
        handleTargetPageType(callback);
      }
    )
  );
}

2. 頁面間跳轉(zhuǎn)

我們只需要在入口文件的 config 配置中指定好 pages芬首,然后就可以在代碼中通過 Taro 提供的 API 來跳轉(zhuǎn)到目的頁面,例如:

// 跳轉(zhuǎn)到目的頁面逼裆,打開新頁面
Taro.navigateTo({
  url: '/pages/page/path/name'
})

// 傳入?yún)?shù) id=2&type=test
Taro.navigateTo({
  url: '/pages/page/path/name?id=2&type=test'
})

3. 狀態(tài)管理

為了減少學(xué)習(xí)成本郁稍,我們沿用RN端使用的狀態(tài)管理機(jī)制mobx進(jìn)行狀態(tài)管理,在Taro端使用方式和RN端完全一致胜宇。這樣也方便已有的RN項目后面能快速遷移到Taro耀怜。

4.樣式管理

樣式管理是多端開發(fā)的一大挑戰(zhàn),因為 React Native 與一般 Web 樣式支持度差異較大桐愉。樣式上 H5 最為靈活财破,小程序次之,RN 最弱从诲,統(tǒng)一多端樣式即是對齊短板左痢,也就是要以 RN 的約束來管理樣式,同時兼顧小程序的限制。
不過這也正巧適用于我們團(tuán)隊俊性,因為我們對RN樣式控制比較熟略步,所以我們并沒有采用scss的方式,而是沿用編寫RN樣式的方式定页,完全使用style來編寫Taro樣式趟薄。

<View
        style={{
          display: 'flex',
          position:'relative',
          width: Taro.pxTransform((global.WINDOW_WIDTH_VALUE - 30) * 2),
          height: Taro.pxTransform(130)
          flexDirection: 'column',
          backgroundColor: '#EAEAEA'
        }}
/>

其中有幾點特殊需要注意:
1.因為RN一般需通過Dimensions 獲取寬高再進(jìn)行換算,Taro提供的 pxTransform() 可解決該問題典徊,但編譯 RN 端樣式文件時并沒有考慮這點杭煎,
所有數(shù)字必須在style中用Taro.pxTransform()包裝,且單位是px。

  1. 在開發(fā)中必須聲明flex布局和排版宫峦,因為RN和小程序H5默認(rèn)橫縱不一樣岔帽。
  2. position: 'absolute'需要在外層父view設(shè)置position: 'relative'。
  3. 覆蓋組件樣式可以通過style傳遞导绷,但style不支持?jǐn)?shù)組犀勒。

最終看一下實現(xiàn)的效果還是能保證高度統(tǒng)一的。


image.png

開發(fā)中遇到的問題

1.語法問題
由于微信小程序端的限制妥曲,有一些jsx用法不能得到很好地支持贾费,比如不能使用 Array#map 之外的方法操作 JSX 數(shù)組、暫不支持在 render() 之外的方法定義 JSX檐盟、不能在 JSX 參數(shù)中使用對象展開符褂萧、不支持無狀態(tài)組件等。
2.房產(chǎn)組件庫依賴問題
因為組件庫中使用了es6語法葵萎,且部分組件有mobx的引用导犹,直接依賴會有不兼容的問題,所以將其統(tǒng)一用babel轉(zhuǎn)成es5格式打包到lib中引用羡忘。
3.跨域問題
RN不存在跨域問題谎痢,小程序網(wǎng)絡(luò)請求需要在小程序設(shè)置中配置,H5存在跨域的問題卷雕,這個可通過 devServer.proxy 解決节猿,以及編譯打包的靜態(tài)資源是固定文件名,建議改成帶 hash 值方便緩存管理漫雕,這些配置在項目里的 src/config 中都能找到滨嘱。

總結(jié)

本文從一個RN開發(fā)者角度來進(jìn)行Taro跨平臺項目實踐,其中在狀態(tài)管理及樣式管理上都采用了和RN一樣的機(jī)制浸间,一是為了組內(nèi)同學(xué)可以快速上手開發(fā)太雨,二是為了將已有rn項目能快速平移到Taro。但是對H5和小程序兩端掌握的能力有限魁蒜,需要實踐積累囊扳。
目前由于組件庫煤墙、sdk積累較少,前期開發(fā)業(yè)務(wù)的同時要同時進(jìn)行業(yè)務(wù)開發(fā)宪拥、底層封裝、腳手架調(diào)整铣减,業(yè)務(wù)開發(fā)速度因此受到影響她君,粗略的估算是RN同等業(yè)務(wù)開發(fā)時間的2倍左右。預(yù)計未來理想情況下葫哗,基于完整的組件庫缔刹、sdk、以及標(biāo)準(zhǔn)化的協(xié)議可以抹平系統(tǒng)劣针、平臺校镐、端之間的大多數(shù)差異,開發(fā)效率趨近于目前RN情況捺典。
小程序各端統(tǒng)一技術(shù)棧到Taro后鸟廓,兩方理論上可以做到無縫銜接,底層資源共享襟己、復(fù)用引谜,這將顯著提升開發(fā)效率,后續(xù)我們在推動四端邏輯擎浴、協(xié)議統(tǒng)一的同時员咽,會加強(qiáng)組間溝通,與FE同學(xué)共同促進(jìn)通用層優(yōu)化贮预,使業(yè)務(wù)開發(fā)者可以專注于業(yè)務(wù)本身贝室。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仿吞,隨后出現(xiàn)的幾起案子滑频,更是在濱河造成了極大的恐慌,老刑警劉巖茫藏,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误趴,死亡現(xiàn)場離奇詭異,居然都是意外死亡务傲,警方通過查閱死者的電腦和手機(jī)凉当,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來售葡,“玉大人看杭,你說我怎么就攤上這事⌒铮” “怎么了楼雹?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我贮缅,道長榨咐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任谴供,我火速辦了婚禮块茁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桂肌。我一直安慰自己数焊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布崎场。 她就那樣靜靜地躺著佩耳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谭跨。 梳的紋絲不亂的頭發(fā)上干厚,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天,我揣著相機(jī)與錄音饺蚊,去河邊找鬼萍诱。 笑死,一個胖子當(dāng)著我的面吹牛污呼,可吹牛的內(nèi)容都是我干的裕坊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼燕酷,長吁一口氣:“原來是場噩夢啊……” “哼籍凝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苗缩,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤饵蒂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后酱讶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體退盯,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年泻肯,在試婚紗的時候發(fā)現(xiàn)自己被綠了渊迁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡灶挟,死狀恐怖琉朽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情稚铣,我是刑警寧澤箱叁,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布墅垮,位于F島的核電站,受9級特大地震影響耕漱,放射性物質(zhì)發(fā)生泄漏算色。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一螟够、第九天 我趴在偏房一處隱蔽的房頂上張望剃允。 院中可真熱鬧,春花似錦齐鲤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捧灰,卻和暖如春淆九,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毛俏。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工炭庙, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人煌寇。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓焕蹄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阀溶。 傳聞我的和親對象是個殘疾皇子腻脏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359