如果你使用 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程式碼實現了 BaseComponent
和 createComponent
,BaseComponent
的作用主要是重寫了react裡面的render、setState等核心程式碼,createComponent
主要作用是呼叫 Component()
構建頁面,處理以下等對接工作完成適配:
- 將元件的 state 對應為小程式元件配置物件的 data
- 將元件的生命週期對應為小程式元件的生命週期
- 將元件的事件處理函式對應為小程式的事件處理函式
- ...
簡單來說就是先將程式碼編譯成各個平臺結構化語言的程式碼,然後通過介面卡模式等等方法適配到各個平臺能夠讓之執行起來,整個Taro2的架構編譯時做的工作佔主要部分,執行時工作量較小。
總結
- 重編譯時,輕執行時:這從兩邊程式碼行數的對比就可見一斑。
- 編譯後程式碼與 React 無關:Taro 只是在開發時遵循了 React 的語法。
- 直接使用 Babel 進行編譯:這也導致在工程化和外掛方面的羸弱。
Taro3
Taro3可以大致理解為解釋型架構,這個工作就主要是在執行時"對程式碼進行解釋",怎麼理解呢?升級為Taro後你可以發現package.json
檔案裡面多了個(當然不止這一個)@taro/runtime
的依賴,開啟包所在目錄:
驚奇的發現了我們在web中才會有的bom
跟dom
相關的關鍵字,原來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中可能遇到的報錯
- Error: module "app.js" is not defined;module "common.js" is not defined (分包後公共元件打包問題)
- webpack Parser.pp$4.raise 報錯 (webpack環境編輯使用的DOMAIN='',應該使用DOMAIN='""')
- Can't resolve './style/index.scss' (taro ui2.+和taro3.+版本不相容)
- TypeError: Cannot read property 'prototype' of undefined (注意第三方庫的依賴引用導致不相容問題)
- MobX,Store Is Not Available, Make Sure It Is Provided By Some Provider (入口檔案app.js使用<Provider {...store}></Provider>)注入全域性store
- TypeError: Cannot read property 'getBehaviorPageData' of undefined (https://github.com/NervJS/tar...)(Taro.getCurrentPages獲取到的page例項上沒有$component)
- taro路徑別名需要在webpack也配置一下
- 樣式錯亂問題,taro3中如不使用css module,則預設全域性樣式。
開發Taro2注意事項
憑記憶寫下幾點,具體問題可能與版本號也有關係,僅供參考
- 有狀態元件儘量使用類元件開發,在頁面中尤其注意這點,函式式元件中hooks和一些api功能bug較多,甚至某些鉤子出現不執行的bug
- 函式式元件中無法通過
Taro.createSelectorQuery
獲取元素資訊,因為taro2需要傳遞scope,而在函式元件中無法獲取,及時使用useScope
useEffect
和useEffectLayout
執行時機與普通的react專案有區別useDidShow
在抖音端不執行Taro.hideLoading
微信真機上會同時關閉Taro.showToast
- 元件中不能匯出常量,如果需要常量,需要另開一個檔案單獨匯出使用
- jsx中渲染函式字首必須是render開頭,如
renderItem = () => xxx
- Jsx中使用switch語法不能用default分支,會提示你邏輯多餘
- jsx中試用if..else..語法渲染節點,有時候並不會如你所意,嘗試定義一個變數node,在分支中賦值給node後,最終return這個node節點解決問題
- 如果使用了mobx,抖音端在jsx條件渲染的時候需要用
toJS
包一下,如:toJS(list).length > 0 && <View>xxx</View>
Swiper
元件的nextMargin
和previousMargin
屬性在h5端無效- h5有時候容器高度會比小程式呈現的高,需設定
box-sizing: border-box;
- 不使用
View
、Text
標籤選擇器,H5內不生效。解決方案:使用className
開發Taro3注意事項
憑記憶寫下幾點,具體問題可能與版本號也有關係,僅供參考
更新元件會把兄弟節點也重新渲染了,問題描述
解決方案:給兄弟節點或自身增加層級
- 使用
Taro.createAnimation
,animationData
的初始值不能為null
,否則過度動畫的效果會失效,直接呈現最終的樣式,解決這一問題可以使用{}
- 版本3.4.0之前
Taro.createAnimation
在H5端失效 - 由於層級過深導致的渲染問題可以嘗試使用
CustomWrapper
元件包裹,它的作用是建立一個原生自定義元件。對後代節點的setData
將由此自定義元件進行呼叫,達到區域性更新的效果,從而提升更新效能。 - video元件有時在第一次渲染的時候獲取不到時長,無法自動播放問題。
- 偽元素和偽類在小程式端失效
- 全域性樣式問題,taro3中如果不是css module,則預設樣式檔案作用所有元件
Image
在h5的實現是有一層包裝層,這點在taro2中同樣存在ScrollView
中使用position: sticky
無效,這點在taro2中同樣存在- hidden異常,通過
state
資料控制顯隱,不會生效。解決方案:通過三目運算解決