導讀css
由 58 前端團隊主導的 Taro 3 適配 React Native 工做已完成有一段時間了。目前發佈了多個體驗版,也將在3月底迎來正式版。基於 Taro 的良好架構演變,適配 React Native 的方案的也作了較大調整,本文將主要介紹 Taro 3 適配 React Native 運行時相關的詳細設計與實現。html
背景前端
Taro 已經進入3.0 時代,相對於 Taro 1/2 來講,採用重運行時架構,可讓開發者可以得到完整的 React/Vue 等框架開發體驗,所以,咱們在設計 Taro3 React native 的方案時,也是基於運行時方案,增長 taro-runtime-rn 包來適配 React Native 端,使得 Taro 標準的 React 代碼可運行在 React Native 端,讓開發者能夠低成本的擴展到 React Native 端。react
方案設計git
Taro 3.0 是近乎全運行時方案,在設計整個架構時,從瀏覽器的角度去思考,不管是開發框架是什麼, React 也好, Vue 也罷,最終代碼通過運行以後,都是調用瀏覽器的 BOM/DOM 的 API,所以,對於小程序端,Taro 團隊增長 taro-runtime 包,在這個包中實現一套高效、精簡版的 DOM/BOM API, 當運行在小程序端時,也有一套高效的 DOM/BOM API,從而實現了跨框架開發方案。
詳細內容參考( https://mp.weixin.qq.com/s/5pdUD9YNojgvZBSve5-2EA )github
在設計 Taro3 React Native 方案之初,咱們但願能夠與小程序端標準較爲一致的方案,對比了兩種方案:小程序
-
支持 React, Vue 開發,與小程序的設計思路一致,讓 React Native 去模擬瀏覽器的 BOM/DOM API ,實現 React Native 的 renderreact-native
-
支持 React 開發,經過編譯和運行時去適配 Taro 的寫法api
以上兩種方案,若是採用基於小程序的方案,會存在如下問題:瀏覽器
-
基於小程序的方案,運行在 React Native 端,性能會有所降低,且方案更加複雜化,維護成本過高
-
脫離 React Native 生態,好比一些本來可直接使用的組件,須要作一層適配纔可以使用
所以,咱們採用第二種方案,更好的貼近 React Native 生態,經過編譯和運行時適配,讓 Taro3 的 React 代碼能夠方便的擴展到 React Native 端。
詳細設計
Taro3 React Native 總體方案的設計思路:基於 Taro 源碼,利用 Metro 打包直接生成 jsBundle,經過編譯和運行時適配到 Taro 的寫法。
(https://mp.weixin.qq.com/s/-7G7NMHX8ol99QxkswFOxg)
直接基於源碼去打包運行時適配,如何作適配,須要適配哪些內容?
-
入口文件及配置,Taro 入口寫法是基於小程序的方案,需將其轉換爲 React Native 的入口及路由導航系統
-
頁面的配置,對下拉屬性,滾動,頁面 Title 等相關設置
-
生命週期, componentDidShow, componentDidHide 的支持
-
頁面函數,onPullDownRefresh, onPageScroll 等
-
事件、Taro 自定義 Hooks、 Current 對象
-
樣式的支持,這裏不過多的贅述,後續會有詳細的文章說明
方案實現
Taro3 React Native 是總體方案是利用 Metro 基於 Taro 源碼打包。
Metro 是針對 React Native 的 JavaScript 模塊打包工具,接收一個入口文件和打包配置,將項目中全部依賴打包在一個或多個js文件。
打包過程會分爲三個階段:
-
Resolution:構建模塊的依賴圖,處理模塊的依賴關係
-
Transformation:負責將模塊轉換成目標平臺能夠理解的格式
-
Serialization:模塊轉換序列化,組合這些模塊來生成一個或多個包
對於 Taro 寫法的支持,咱們在 Transformation 轉化階段,經過自定義的 taro-rn-transformer 與 taro-rn-style-transformer 對 Taro 的代碼進行轉換。
-
在編譯階段,頁面源文件都會進入到自定義的 taro-rn-transformer ,在 rn-transformer 中,會根據編譯配置,一是,入口及頁面會
注入運行時處理函數, 二是,在 React Native 中,樣式並無全局概念,對於 Taro 中定義的全局樣式,好比 app.scss 等,在進入到 rn- transformer,會全局樣式引入到頁面中,支持到全局樣式。 -
在代碼運行階段,運行時處理函數會適配到Taro的相關內容,包含動態構建導航,頁面配置,生命週期函數等相關內容,完成對入口,導航與頁面的支持。
Taro 3 React Native中,運行時方案主要包含三個模塊 ,各個模塊之間的關係:
-
taro-router-rn,基 React Navigation 對路由進行封裝,提供動態建立導航的方法給運行時,而且封裝導航相應的API內容。
-
taro-runtime-rn,運行時的核心內容,主要提供兩個包裝方法,一是 createReactNativeApp,用來對頁面入口的相關處理,二是 createPageConfig,頁面的包裝方法,完成對頁面的適配,包含生命週期,頁面的配置等。
-
taro-rn-transformer, 編譯時注入頁面的包裝方法和入口方法,並將入口的全局樣式注入到頁面中。
對於 Taro 運行時的適配的內容,如圖所示:
入口文件支持
在 React Native 中,AppRegistry 是全部 React Native 應用的 JS 入口,經過 AppRegistry.registerComponent 方法註冊根組件,如有多個頁面,在根組件中創建對應導航系統。
在 Taro 中,入口是按照小程序方案來定義,要運行在 React Native 端,需將這些配置轉換爲 React Native 相關的配置,生成可運行在 React Native 的入口文件。
Taro 中入口文件:
//app.config.tsexport default { pages:[ 'pages/index/index', 'pages/index/about' ], window:{ backgroundTextStyle: 'light', .... }, tabBar:[...]}//app.tsxexport defalut class App extends Component{ ... render(){ return this.props.children }}
咱們實現方案的基本思路是:
讀取 app.config ,獲取到對應的頁面信息,將頁面在入口文件引入,創建起引用關係,根據頁面路徑轉換爲駝峯的形式來做爲頁面名稱,生成構建導航系統的路由配置。
運行時模塊會提供一個入口包裝的函數,將全局配置,轉換後的路由配置,動態的構建入口根組件。
轉換後的入口文件代碼:
import { AppRegistry } from 'react-native';import { createReactNativeApp } from '@tarojs/runtime-rn'import App from './src/app'import pagesIndexIndex from './src/pages/index/index';import pagesIndexAbout from './src/pages/index/about'var config = {"appConfig":{"pages":["pages/index/index","pages/index/about"],"window":{"backgroundTextStyle":"light","navigationBarBackgroundColor":"#fff"}}}const routers = [ { name:'pagesIndexIndex', component: pagesIndexIndex },{ name:'pagesTabbarHome', component: pagesIndexAbout }]AppRegistry.registerComponent('app',createReactNativeApp(App,{config,routers})
運行時調用 createReactNativeApp 函數,在這個函數完成初始化,這個函數裏主要作了些什麼?
-
根節點Provider的注入
-
導航初始化
function createReactNativeApp (App,config){ return class Entry extends React.Component{ ... render(){ return React.createElement(TCNProvider, { ...this.props }, React.createElement(App, { ...props }, createRouter(config.routerConfig) )) } }}
-
對於根節點Provider注入,因爲在 taro-component-rn 的 Picker 組件是封裝的 Ant-Design 組件,須要注入 Ant-Design 的 Provider
-
對於導航系統初始化,Taro 3 仍然是採用 React Navigation,和 Taro 1/2 的差異是,升級到了 5.x 的版本。封裝導航模塊,根據轉換生成的路由配置,提供 createRouter 的方法,動態去建立路由節點,構建出導航系統
頁面支持
實現對頁面支持,其基本思路和入口一致的,在編譯階段,注入頁面包裝的函數,在運行時階段,完成頁面配置,頁面函數等相關支持。
import { createPageConfig } from '@tarojs/runtime-rn'//頁面文件import pagesIndexIndex from './src/pages/index/index'//頁面configimport pageConfig from './src/pages/index/index.config'export default createPageConfig(pagesIndexIndex,pageConfig)
在 Taro 頁面組件中,根據頁面適配,需實現對如下兩個內容的支持。
-
頁面函數,包括了 onReactBottom, onPullDownRresh 等
-
生命週期函數,包含了 componentDidShow, componentDidHide,這兩個函數對應小程序的 onHide, onShow
在 React Native 端,也保持和 Taro 的 React 組件寫法是徹底一致, 經過運行時函數 createPageConfig,實現對於面函數與生命週期函數的支持。
頁面函數支持
對於微信的頁面函數,根據頁面config配置文件來控制是否觸發,
disableScroll 是否可滾動, enablePullDownRresh 是否開啓下拉刷新。
-
對於onPageScroll,onReachBottom , onPullDownRresh 都與頁面滾動相關聯, 當 config 配置 disablecroll 不爲true時,對應的頁面最外層會用 ScrollView 包含對應的頁面組件,實現對頁面函數的支持
-
onPageScroll, 經過監聽 ScrollView 的 onScroll 方法實現
-
onReachBottom, 監聽頁面滾動動畫結束函數 onMomentumScrollEnd ,來判斷當前的離底部高度,最終來觸發該函數
-
onPullDownRresh, 當enablePullDownRresh爲 true 時,開啓下拉刷新,經過封裝 refreshControl 來實現
function createPageConfig(Component,config){ const WrapScreen = (PageComponent) => { return class PageScreen extends React.Component{ ... render(){ return <ScrollView ... onScroll={()=>{}} refreshControl={<RreshControll />} > <PageComponent /> </ScrollView> } } } return WrapScreen(Component)}
-
onResize , onTabItemTap ,是基於 React Native 現有方案的實現
-
onResize, 在 React Native中,可監聽屏幕高度變化,在 Taro 中,是經過監聽屏幕的寬高變化來觸發該方法
-
onTabItemTap , TabBar 是和導航相關聯,咱們導航是基於 React Navigation 的封裝,監聽導航的 tabPress 方法來觸發 onTabItemTap
生命週期支持
對於生命週期函數 componentDidShow, componentDidHide,這兩個函數的觸發條件:
-
當頁面發生跳轉時
-
當App進行先後臺切換的時
實現上述函數,基本思路:
-
App先後臺切換時,經過監聽 AppState 的狀態變化,狀態切換的變化,可判斷是從前臺到後臺,從而來觸發對應的函數
-
咱們的路由導航系統是基於 React Navigation, 頁面切換時,導航提供了頁面聚焦和是失去焦點時觸發 focus 與 blur 事件,經過監聽這兩個事件,判斷當前頁面是否可見來觸發對應函數
Current對象
在 Taro 3.0 以後,小程序端沒有自定義組件,也再也不有 this.scope 和 this.componentType,this.$router 的概念,對於須要獲取頁面切換的參數,當前頁面的實例對象,經過提供了 getCurrentInstance 方法,返回 Taro 全局變量 Current ,包含路由,應用與頁面實例,包含三個屬性:
-
Current.app,返回當前小程序應用實例
-
Current.page,返回當前小程序頁面實例,
-
Current.router,返回當前小程序路由信息
在 React Native 端,也是調用 getCurrentInstace 方法來返回 Current 對象
-
對於 app 和 page ,返回小程序規範實例,可經過此實例調用小程序規範生命週期。其實現思路是,當頁面切換時建立一個對象,對象包含小程序的生命週期方法,當調用該方法時,經過 ref 關聯到的當前頁面,來 call 當前頁面的方法。
const pageRef = this.screenRef const inst: PageInstance = { config: pageConfig, route: pagePath, onShow () { const page = pageRef.current if (page != null && isFunction(page.componentDidShow)) { page.componentDidShow && page.componentDidShow() } }, ... }
-
對於 router,基於 React Navigation 導航 獲取到路由參數,返回到 router 對象中,目前暫不支持 onReady 等生命週期方法
原生 React Native 應用支持
有開發者提到,對於目前已經存在的 React Native 項目,在不修改原來的頁面和導航的前提下,是否能夠接入Taro?
答案是確定的,基於 Taro 3 總體的設計方案,與現有業務的結合接入,咱們也給出了對應的方案。
對於已有 React Native 項目接入 Taro,須要支持如下幾點:
-
與原有頁面一塊兒打包方案結合
-
路由須要統一處理
-
支持 Taro 的編譯配置,頁面函數等
關於打包方案,Taro3 React Native 的打包方案是基於 Metro , 編譯打包會生成支持Taro的 Metro 配置,並與業務配置合併獲得最終的配置進行打包,可以很好的與現有業務進行融合。
關於路由統一處理,Taro React Native 的路由是基於頁面的配置,封裝的React Navigation的方案,與現有業務的路由結合,入口仍然按照原來的方式,Taro 頁面路由可自行加入,完成路由的處理。
所以,咱們提供了一種比較靈活的接入方案,其基本思路:支持導出 Taro 默認的 Metro 配置,與業務配置合併獲得最終打包配置,提供支持Taro寫法的運行時方法,處理頁面編譯配置,頁面函數等相關內容
-
提供 tarojs/rn-supporter 的包, 導出Taro3 的 Metro 配置,可支持如下內容
-
支持Taro樣式寫法
-
支持Taro編譯配置
-
支持Taro運行時配置
-
支持Taro跨平臺開發方案
-
-
提供運行時的函數,經過直接的調用運行時的包裝函數,完成對頁面內容的支持
-
支持頁面函數,生命週期函數等
-
支持頁面配置
-
import { createPageConfig } from '@tarojs/runtime-rn'import PagesTaroPageApi from './src/pages/taroPage/api'import PagesTaroPageApiConfig from './src/pages/taroPage/api.config'<Stack.Screen name="PagesTaroPageApi" component={createPageConfig(PagesTaroPageApi, { pagePath: '/pages/taroPage/api',...PagesTaroPageApiConfig})} />
這種方案能夠較爲方便的和現有項目結合,但需注意兩點:
-
業務需自行處理導航
-
頁面組件須要運行時函數包裝
總結
Taro3 React Native 是基於 Metro 打包,經過自定義 transformer 來適配 Taro 的樣式和頁面支持,提供運行時函數,能夠方便的支持到 Taro 頁面配置與相關函數 ,更加的方便靈活,也更加貼近React Native生態,也可更方便的與現有業務融合,在不跨端的項目中也可使用,可以大大提高咱們的開發效率。
固然,咱們的方案也還還存在進一步優化的空間,好比支持組件與API運行時自定義擴展,在不一樣的業務中,有些組件和API存在差別性,如地圖,跟業務有必定的關聯性,可按須要接入百度或高德地圖等。
完整實例:http://github.crmeb.net/u/defu
來自 「開源世界 」 ,連接:http://ym.baisou.ltd/post/599.html,如需轉載,請註明出處,不然將追究法律責任。