序
基於雨點兒網,分享react-native開發android app的方法。
上篇文章《零基礎用react-native開發android app》介紹了RN(react-native)的一些基本概念以及開發流程,這篇文章主要結合我的開源專案raindrop-app跟大家交流下我自己的程式碼組織以及開發過程中遇到的一些問題。
程式碼組織:
目錄結構:
.
├── components //組成應用的各個元件
│ ├── Routers.android.js //每個元件若實現不一樣,分為android的實現和ios的實現。
│ ├── Routers.ios.js
│ ├── common //公共元件
│ ├── issues //議題頁面
│ ├── navigation //導航元件,android用側邊欄,ios準備用tab
│ └── project //專案頁面
└── network //網路服務
└── DataService.js
我自己的程式碼全部放在src目錄下,這樣寫程式碼過程中搜尋啊什麼操作比較方便,從邏輯上也比較清晰。
react的應用,是用自定義元件或原生元件層層巢狀而成的。因此我將整個應用劃分為元件部分(組成各個頁面)和一些其他服務(目前比較簡單,只抽象出發get請求的網路服務)。
components內,根據自己的業務邏輯進行抽象,把整個應用劃分為層層巢狀的元件,目錄結構的組織形式基本就是我頁面的組織形式。如果有一些比較通用的功能,可以提取成公共元件,我放在common目錄下。
每個元件如果ios和android的實現不太一樣,則建立兩個檔案,如Routers.android.js和Routers.ios.js。
基本邏輯:
-
根元件:
我定義了一個Routers元件,作為整個app的根元件。Router元件實際上包裝的官方的Navigator元件,主要作用:負責整個app的所有路由,當使用navigator去跳轉路由時,會最終進入renderScene函式來渲染不同的頁面。
提供了預設router,整個程式啟動時,預設載入頁面ProjectList。
各個頁面:不同路由對應不同的頁面,如Routers的renderScene函式中,每個if分支是一個頁面。這些頁面實際上就是一個個匯出的元件。比如ProjectList元件是用來做專案列表的,但他自身又包含了一個用來渲染每個專案單元格的projectCell元件。如此,所有元件都是對上層呈現成一個統一的元件介面,對下層自己去組裝多個不同元件,最終形成一個模組化的統一的app。
-
元件之間的關聯:
元件之間經常會發生關聯。我自己用到了以下情況:-
父改變子:
子通過state對外提供介面,父可以通過setState去改變子的狀態,並讓子重新渲染。state是React的一個很重要的概念。在元件上可以設一些屬性,這些屬性都有一個初始狀態,然後使用者的操作產生互動,只要是用setState去觸發這個元件狀態變化,則會觸發這個元件重新渲染 UI 。
父直接呼叫子匯出的方法,比如官方元件
DrawerLayoutAndroid
提供的openDrawer
方法。可以使用react的refs機制去呼叫。比如我在NavTab元件的openNavDrawer函式中,以this.refs['drawer'].openDrawer();
這樣的函式方式去呼叫。那麼如何像這種方式匯出自己的方法供父元件直接以函式方式呼叫?注意匯出的方法必須是作為類方法就可以了,比如openNavDrawer這個函式就是匯出給父用的。
子呼叫父:
這其實有點類似是反向依賴的設計模式。就是子提供觸發回撥的介面,但是究竟是觸發後執行什麼,子並不關心。比如我封裝的NavToolbar(就是很多介面上面的工具條)元件的onClicked方法。很多地方的按鈕都是返回上一級。 <NavToolbar ... onClicked={() => {this.props.nav.pop();}} /> 但是最底層的幾個介面上的按鈕,換成了彈出側面導航條,以供切換。 <NavToolbar ... onClicked={this.onToolbarClicked} /> 對於這種情況,導航條要想抽象成公共的元件,他就不能依賴於他的父究竟是哪個介面。觸發的具體動作就需要通過回撥注入進來,這時就用這種方式。
兄弟關係:
在共同的父中組合上面兩種情況就可以了。比如ProjectList.android.js中onToolbarClicked: function (){ this.refs['navTab'].openNavDrawer(); }, <NavTab ref='navTab' nav={this.props.nav}> <NavToolbar icon={"ic_menu_white"} title={'專案'} onClicked={this.onToolbarClicked} /> {content} </NavTab>
其他情況:
參考這篇文章,不過目前我還沒用到這種毫無關係的事件觸發,所以尚未研究。
-
除錯
chrome除錯:
安裝react dev的chrome官方外掛。在手機上設定host的ip,點選start chrome debugging。 chrome會自動跳轉到除錯地址,在瀏覽器上開啟除錯視窗,會發現裡面多了一個react頁籤。inspect元素:
在模擬器中開啟inspect element皮膚,點選模擬器中的元素,chrome會跳轉到對應dom。-
槽點:
在瀏覽器改動css後,模擬器的佈局不跟著更新。注意每個dom都有個RN的包裹,需要更改這個以RCT開頭的包裹元素。參考issue。
瀏覽器的dom和手機上的元素位置對不準確。我有時會分不清哪個dom對應我螢幕哪一塊。
除錯經常失效,除錯視窗的react頁籤動不動就找不到了,我大部分時候是直接改程式碼,在模擬器看效果的。
遇到的坑:
模擬器中的程式經常崩潰,程式碼語法有低階錯誤,一但
reload js
,程式就有很大概率崩潰,需要react-native run-android
重新開始。換工程執行專案,react-native run-android 前最好關下後臺,否則兩個專案會互相影響。
出錯提示很不完善。
比如有時我會將<View>
誤寫成<view>
,或者忘記關閉標籤。而這些低階錯誤,RN裡面往往會非常難排除,提示往往都很奇怪,我都是靠走讀程式碼發現。
比如有一次,我看了ECMAScript 6 Features的語法後,將DataService中var SERVER = 'http://www.yudianer.com/api';
這句改成了const SERVER = 'http://www.yudianer.com/api';
,當時沒發現什麼問題。但後面發現了奇怪的問題,只有在瀏覽器除錯的時候,app才能正常執行,否則什麼也不顯示,而且沒有任何提示。最後打包執行無數次都沒反應,只能一點一點註釋程式碼排除,才發現是我用了ECMAScript 6 Features,卻沒有配置。。。-
RN的有些元件有些限制,往往是後知後覺。例如:
DrawerLayoutAndroid這個元件外面不能再包一個
<View></View>
。如果你不幸這麼做了,會整個頁面不顯示了,而沒有任何提示。。。如果ListView包在一個View中,那麼外面這個View需要設定style={flex: 1}。否則ListView將不能滾動。
當遇到這種問題,最好去google一下,或去github看下有沒有類似的議題。實在不行就通過註釋程式碼的方法排除。
JSX的語法經常搞錯,跟一般的模板語言不太一樣。比如:
renderProject: function(project){
return(
<ProjectCell
onSelect={() => this.selectProject(project)}
project={project}/>
);
},
我會經常忘記這是個函式,而直接寫成:
renderProject: function(project){
<ProjectCell
onSelect={() => this.selectProject(project)}
project={project}/>
},
這看上去沒什麼,問題是這種類似錯誤的提示很奇怪,不好定位。
總結:
RN在android上確實不太完善,除錯工具,錯誤提示,文件等都不是很友好。但去學習下還是挺酷的,而且在facebook不遺餘力的推動,相信會越來越完善的。