Taro原理分析、遷移指南及開發注意事項

MangoGoing 發表於 2022-01-25
如果你使用 Taro 開發感覺 Bug 少,那說明你的 React 程式碼寫得很規範。 -- Taro團隊

趁前段時間做基於taro2的微店鋪以及taro2升taro3的專案經驗,簡單介紹Taro2跟Taro3的各自的優缺點以及實際使用場景下的語法區別,並分享Taro3升級中和使用中的踩坑點。

推薦閱讀

小程式跨端框架開發的探索與實踐

Taro1/2

Taro3之前的整體架構可以看成兩部分:編譯時和執行時。這裡解釋一下兩者的用途:

編譯時:

通過對⽤戶的 React 程式碼進⾏編譯來轉化程式碼語法,如jsx轉小程式xml等,甚至轉換成各個平臺(抖音小程式、微信小程式、H5等等)都可以運⾏的程式碼。編譯時工作流程主要是通過babel 將 Taro 程式碼解析成抽象語法樹,然後操作語法樹生成目標平臺的程式碼,也就是parse -> replace -> generate這樣一個工作流程。

build:weapp編譯微信小程式端為例:

render() {
  return (
      <View>
      {
        dataList.map((data, index) => (<Text key={index}>{data.title}</Text>))
      }
    </View>
  )
}

經過babel轉換後:

<view wx:for="{{dataList}}" wx:for-item="data" wx:for-index="index">
  <text>{data.title}</text>
</view>
我們都知道 JSX 是一個 JavaScript 的語法擴充套件,它的寫法千變萬化,十分靈活。這裡我們是採用 窮舉 的方式對 JSX 可能的寫法進行了一一適配,這一部分工作量很大,實際上 Taro 有大量的 Commit 都是為了更完善的支援 JSX 的各種寫法

這是摘自官網對taro2編譯時的一句描述,由於使用窮舉的適配方式,勢必會造成jsx的各種各樣的奇怪的bug產生和各種開發時的限制。

執行時

可以知道的是,我們開發taro專案時,引用的是taro庫下面的api和元件,呼叫類似微信原生的api,如: wx.getSettings,在taro裡面需要從 @taro/taro引用,然後呼叫 Taro.getSettings,元件則是通過@taro/components引用。這是因為Taro制定了一套執行時的標準元件庫和api,通過對原生api進行擴充和配合編譯時已經抹平了狀態、事件繫結、頁面配置和生命週期等的差異,完成了框架的適配工作。具體點描述:

編譯後的taro程式碼實現了 BaseComponentcreateComponentBaseComponent的作用主要是重寫了react裡面的render、setState等核心程式碼,createComponent 主要作用是呼叫 Component() 構建頁面,處理以下等對接工作完成適配:

  • 將元件的 state 對應為小程式元件配置物件的 data
  • 將元件的生命週期對應為小程式元件的生命週期
  • 將元件的事件處理函式對應為小程式的事件處理函式
  • ...

簡單來說就是先將程式碼編譯成各個平臺結構化語言的程式碼,然後通過介面卡模式等等方法適配到各個平臺能夠讓之執行起來,整個Taro2的架構編譯時做的工作佔主要部分,執行時工作量較小。

總結

  • 重編譯時,輕執行時:這從兩邊程式碼行數的對比就可見一斑。
  • 編譯後程式碼與 React 無關:Taro 只是在開發時遵循了 React 的語法。
  • 直接使用 Babel 進行編譯:這也導致在工程化和外掛方面的羸弱。

Taro3

Taro3可以大致理解為解釋型架構,這個工作就主要是在執行時"對程式碼進行解釋",怎麼理解呢?升級為Taro後你可以發現package.json檔案裡面多了個(當然不止這一個)@taro/runtime的依賴,開啟包所在目錄:

Taro原理分析、遷移指南及開發注意事項

驚奇的發現了我們在web中才會有的bomdom相關的關鍵字,原來Taro3自己實現了一套類似瀏覽器的BOM/DOM那一套API,通過webpack的plugin注入到小程式的邏輯層,打包編譯後,你最終的程式碼都是基於BOM/DOM這幾個API來實現你的具體功能,比如:不管什麼平臺,都有自己一套元素dom的規則,都有各自平臺的類似bom的全域性api規則,Taro3做的就是整合這些廠家的規則封裝為類似BOM/DOM的思想去用,也就是說,我不管你開發時用的什麼框架,我只要保證你執行時能幫你適配到各個平臺即可。

這樣做的最直觀的好處就是前面說到的,不再受限制與框架本身了,理論上來說,Taro不僅可以支援Vue和React,也能用jquery、Angular等等的庫進行跨端開發。站在React的使用者角度:通過taro2和taro3兩個專案開發經驗來說還有一點最直觀的感受,taro3中寫JSX更加舒服了!其實就更加友好的支援JSX這一點,應該是順理成章的,因為taro的架構其實就是無限接近於React的開發體驗,適配的工作是通過執行時的BOM/DOM去完成的,而不是像之前版本一樣,通過窮舉 的方式對 JSX 的寫法進行適配。

有個關鍵點:既然Taro3自己實現了BOM/DOM這一套api,而react的中的渲染器,如react-dom中呼叫的是瀏覽器的BOM/DOM的api,那taro肯定會有自己一套渲染器來連結react的協調器(reconciler,diff演算法所在階段)和taro-runtime 的 BOM/DOM api。原始碼路徑:@tarojs/react,description裡面的描述如下:"like react-dom, but for mini apps."

如何選擇

從上述Taro 3架構來說,改變了Taro1/2的重編譯時的局面從而轉向重執行時,其實重編譯時的侷限性不僅是對開發者的開發規範有更大的限制,也不利於維護taro框架的原始碼,因為一旦各廠商增刪改了她們小程式的某個規範或者api時,要去維護升級taro的程式碼成本是比較高的。

但這不意味著taro3一定是最適合的版本,同等條件下,編譯時做的工作越多,也就意味著執行時做的工作越少,效能會更好。隨時時代的進度,硬體的條件越來越好,犧牲硬體的前提下能夠帶來更好的開發體驗也許是比較明智的選擇,但是在邏輯比較複雜或者需要更多執行記憶體的小程式,還是要側重於重編譯時的Taro2!

遷移指南

升級Taro Next前,請仔細閱讀官方遷移指南

轉換外部樣式 withExternalStyle

Taro1/2 中需指定addGlobalClass才能使用外部的 className,但是 Taro3 提升了所有的樣式檔案到 common.css 中,所以需要對外部樣式進行轉換。

轉換 hooks withHooks

Taro1/2 中所有的 hooks 和一些特殊的功能元件如 Component, memo 等從 Taro 中匯出。但 Taro3 中只維護了 Taro 獨有的 hooks 和功能元件,所以將這些從 Taro2 中拆分出來。

轉換類元件生命週期

React 中有一些廢棄的生命週期需要加上 UNSAFE 的字首標識。

升級 mobx4 到 mobx6

如專案使用了mobx,請注意此條規則。

裝飾器目前方案不穩定,在 mobx6 中更是減少了裝飾器的使用。而且 Taro3 中使用 mobx-react 而不是由 Taro 維護的 mobx-taro。

轉換頁面配置

頁面配置檔案單獨拆分出來。

轉換路由引用

訪問路由和路由引數使用 Taro3 的新的 api。

轉換作用域引用 withScope

Taro1/2 使用編譯時的框架,Taro3 使用的是執行時的框架,所以 scope 直接廢棄了。在轉換中嘗試進行相容處理。

轉換 hidden 屬性

Taro1/2 中廢棄了自定義的一個屬性 hidden。

將全域性樣式的 css 修改為 module.less

Taro3 中為了減少元件樣式檔案的痛點,將所有的單獨作用域的樣式檔案全部都提升到了全域性。直接轉換後如不使用 css module,則會產生大量的樣式混亂干涉。所以為了保持樣式的一致性,需要把樣式進行轉換,將 css 轉換為 css module。

package.json所依賴的 taro 極其相關的依賴手動更改(截止 2022-01-25,Taro 最新版 3.4.0)

{
  "@tarojs/cli": "3.3.17",
  "@tarojs/components": "3.3.17",
  "@tarojs/react": "3.3.17",
  "@tarojs/runtime": "3.3.17",
  "@tarojs/taro": "3.3.17",
}

刪除掉 taro1/2 中的 config/index.js 中的關於 babel 的配置,新增檔案babel.config.js

module.exports = {
  presets: [
    [
      'taro',
      {
        framework: 'react',
        ts: true,
      },
    ],
  ],
};

配置 config/index.js 中,指定使用的框架為 react

{
  framework: 'react',
}

修改專案入口檔案 app.ts

// 修改render函式,預設render this.props.children
render() {
    return this.props.children
}
// 修改匯出,直接將App匯出
export default App;
// Taro.render(<App />, document.getElementById('app'));

原生元件的使用

“Taro3 的元件是沒有配置檔案的,因此 usingComponents 必須配置在“頁面”的配置檔案中”。但是 Taro1/2 的元件允許宣告配置檔案,所以在 Taro3 中需要把這些原生元件的配置宣告提升到具體引用具體的頁面。可參考 #withUsingComponentForHaicaoyun

元件標籤廢棄

在 Taro1/2 中,對應的元件將會編譯成同名的元件。例如 HcyButton 元件編譯成 hcy-button,所以如果專案中處理樣式檔案時對對應的樣式做了定義,那麼在 Taro3 中需要手動進行修改。

page.$component的使用

在 Taro1/2 中允許通過$component 的方式訪問具體元件的 Taro 例項,進而可以訪問類元件的任意方法和屬性,甚至修改其狀態。例如下面示例,訪問上個頁面的例項,並設定其狀態

const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];

const prevPageInstance = prevPage.$component;
prevPageInstance.setState({
  formItems: [],
});

這種在 Taro3 中並沒有簡單的替換方案,只能對所有使用這部分的地方進行一種合適的重構

taro2升taro3中可能遇到的報錯

  1. Error: module "app.js" is not defined;module "common.js" is not defined (分包後公共元件打包問題)
  2. webpack Parser.pp$4.raise 報錯 (webpack環境編輯使用的DOMAIN='',應該使用DOMAIN='""')
  3. Can't resolve './style/index.scss' (taro ui2.+和taro3.+版本不相容)
  4. TypeError: Cannot read property 'prototype' of undefined (注意第三方庫的依賴引用導致不相容問題)
  5. MobX,Store Is Not Available, Make Sure It Is Provided By Some Provider (入口檔案app.js使用<Provider {...store}></Provider>)注入全域性store
  6. TypeError: Cannot read property 'getBehaviorPageData' of undefined (https://github.com/NervJS/tar...)(Taro.getCurrentPages獲取到的page例項上沒有$component)
  7. taro路徑別名需要在webpack也配置一下
  8. 樣式錯亂問題,taro3中如不使用css module,則預設全域性樣式。

開發Taro2注意事項

憑記憶寫下幾點,具體問題可能與版本號也有關係,僅供參考

  1. 有狀態元件儘量使用類元件開發,在頁面中尤其注意這點,函式式元件中hooks和一些api功能bug較多,甚至某些鉤子出現不執行的bug
  2. 函式式元件中無法通過Taro.createSelectorQuery獲取元素資訊,因為taro2需要傳遞scope,而在函式元件中無法獲取,及時使用useScope
  3. useEffectuseEffectLayout執行時機與普通的react專案有區別
  4. useDidShow在抖音端不執行
  5. Taro.hideLoading 微信真機上會同時關閉 Taro.showToast
  6. 元件中不能匯出常量,如果需要常量,需要另開一個檔案單獨匯出使用
  7. jsx中渲染函式字首必須是render開頭,如renderItem = () => xxx
  8. Jsx中使用switch語法不能用default分支,會提示你邏輯多餘
  9. jsx中試用if..else..語法渲染節點,有時候並不會如你所意,嘗試定義一個變數node,在分支中賦值給node後,最終return這個node節點解決問題
  10. 如果使用了mobx,抖音端在jsx條件渲染的時候需要用toJS包一下,如: toJS(list).length > 0 && <View>xxx</View>
  11. Swiper元件的nextMarginpreviousMargin屬性在h5端無效
  12. h5有時候容器高度會比小程式呈現的高,需設定box-sizing: border-box;
  13. 不使用 ViewText 標籤選擇器,H5內不生效。解決方案:使用className

開發Taro3注意事項

憑記憶寫下幾點,具體問題可能與版本號也有關係,僅供參考

  1. 更新元件會把兄弟節點也重新渲染了,問題描述

    解決方案:給兄弟節點或自身增加層級

  2. 使用Taro.createAnimationanimationData的初始值不能為null,否則過度動畫的效果會失效,直接呈現最終的樣式,解決這一問題可以使用{}
  3. 版本3.4.0之前Taro.createAnimation在H5端失效
  4. 由於層級過深導致的渲染問題可以嘗試使用CustomWrapper元件包裹,它的作用是建立一個原生自定義元件。對後代節點的 setData 將由此自定義元件進行呼叫,達到區域性更新的效果,從而提升更新效能。
  5. video元件有時在第一次渲染的時候獲取不到時長,無法自動播放問題。
  6. 偽元素和偽類在小程式端失效
  7. 全域性樣式問題,taro3中如果不是css module,則預設樣式檔案作用所有元件
  8. Image在h5的實現是有一層包裝層,這點在taro2中同樣存在
  9. ScrollView中使用position: sticky無效,這點在taro2中同樣存在
  10. hidden異常,通過 state 資料控制顯隱,不會生效。解決方案:通過三目運算解決

Taro原理分析、遷移指南及開發注意事項