react-native專案結構介紹

caige發表於2015-10-30

基於雨點兒網,分享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
  1. 我自己的程式碼全部放在src目錄下,這樣寫程式碼過程中搜尋啊什麼操作比較方便,從邏輯上也比較清晰。

  2. react的應用,是用自定義元件或原生元件層層巢狀而成的。因此我將整個應用劃分為元件部分(組成各個頁面)和一些其他服務(目前比較簡單,只抽象出發get請求的網路服務)。

  3. components內,根據自己的業務邏輯進行抽象,把整個應用劃分為層層巢狀的元件,目錄結構的組織形式基本就是我頁面的組織形式。如果有一些比較通用的功能,可以提取成公共元件,我放在common目錄下。

  4. 每個元件如果ios和android的實現不太一樣,則建立兩個檔案,如Routers.android.jsRouters.ios.js

基本邏輯:

  1. 根元件:
    我定義了一個Routers元件,作為整個app的根元件。Router元件實際上包裝的官方的Navigator元件,主要作用:

    • 負責整個app的所有路由,當使用navigator去跳轉路由時,會最終進入renderScene函式來渲染不同的頁面。

    • 提供了預設router,整個程式啟動時,預設載入頁面ProjectList

  2. 各個頁面:不同路由對應不同的頁面,如RoutersrenderScene函式中,每個if分支是一個頁面。這些頁面實際上就是一個個匯出的元件。比如ProjectList元件是用來做專案列表的,但他自身又包含了一個用來渲染每個專案單元格的projectCell元件。如此,所有元件都是對上層呈現成一個統一的元件介面,對下層自己去組裝多個不同元件,最終形成一個模組化的統一的app。

  3. 元件之間的關聯:
    元件之間經常會發生關聯。我自己用到了以下情況:

    • 父改變子:

      • 子通過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>
    • 其他情況:
      參考這篇文章,不過目前我還沒用到這種毫無關係的事件觸發,所以尚未研究。

除錯

  1. chrome除錯:
    安裝react dev的chrome官方外掛。在手機上設定host的ip,點選start chrome debugging。 chrome會自動跳轉到除錯地址,在瀏覽器上開啟除錯視窗,會發現裡面多了一個react頁籤。

  2. inspect元素:
    在模擬器中開啟inspect element皮膚,點選模擬器中的元素,chrome會跳轉到對應dom。

  3. 槽點:

    • 在瀏覽器改動css後,模擬器的佈局不跟著更新。注意每個dom都有個RN的包裹,需要更改這個以RCT開頭的包裹元素。參考issue

    • 瀏覽器的dom和手機上的元素位置對不準確。我有時會分不清哪個dom對應我螢幕哪一塊。

    • 除錯經常失效,除錯視窗的react頁籤動不動就找不到了,我大部分時候是直接改程式碼,在模擬器看效果的。

遇到的坑:

  1. 模擬器中的程式經常崩潰,程式碼語法有低階錯誤,一但reload js,程式就有很大概率崩潰,需要react-native run-android重新開始。

  2. 換工程執行專案,react-native run-android 前最好關下後臺,否則兩個專案會互相影響。

  3. 出錯提示很不完善。
    比如有時我會將<View>誤寫成<view>,或者忘記關閉標籤。而這些低階錯誤,RN裡面往往會非常難排除,提示往往都很奇怪,我都是靠走讀程式碼發現。
    比如有一次,我看了ECMAScript 6 Features的語法後,將DataServicevar SERVER = 'http://www.yudianer.com/api';這句改成了const SERVER = 'http://www.yudianer.com/api';,當時沒發現什麼問題。但後面發現了奇怪的問題,只有在瀏覽器除錯的時候,app才能正常執行,否則什麼也不顯示,而且沒有任何提示。最後打包執行無數次都沒反應,只能一點一點註釋程式碼排除,才發現是我用了ECMAScript 6 Features,卻沒有配置。。。

  4. RN的有些元件有些限制,往往是後知後覺。例如:

    • DrawerLayoutAndroid這個元件外面不能再包一個<View></View>。如果你不幸這麼做了,會整個頁面不顯示了,而沒有任何提示。。。

    • 如果ListView包在一個View中,那麼外面這個View需要設定style={flex: 1}。否則ListView將不能滾動。

    • 當遇到這種問題,最好去google一下,或去github看下有沒有類似的議題。實在不行就通過註釋程式碼的方法排除。

  5. 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不遺餘力的推動,相信會越來越完善的。

相關文章