React Native:使用 JavaScript 構建原生應用

[轉載]javascript

本篇爲聯合翻譯,譯者:寸志範洪春kmokidd姜天意css

數月前,Facebook 對外宣佈了正在開發的 React Native 框架,這個框架容許你使用 JavaScript 開發原生的 iOS 應用——就在今天,Beta 版的倉庫釋出了!html

基於 PhoneGap 使用 JavaScript 和 HTML5 開發 iOS 應用已經有好幾年了,那 React Native 有什麼牛的?html5

React Native 真的很牛,讓你們興奮異常的主要緣由有兩點:java

  1. 能夠基於 React Native使用 JavaScript 編寫應用邏輯,UI 則能夠保持全是原生的。這樣的話就沒有必要就 HTML5 的 UI 作出常見的妥協;node

  2. React 引入了一種不同凡響的、略顯激進但具有高可用性的方案來構建用戶界面。長話短說,應用的 UI 簡單經過一個基於應用目前狀態的函數來表達。react

React Native 的關鍵就是,以把 React 編程模式的能力帶到移動開發來做爲主要目標。它的目標不是跨平臺一次編寫處處執行,而是一次學習跨平臺開發。這個是一個很是大的區別。這篇教程只介紹 iOS 平臺,不過你一旦掌握了相關的概念,就能夠應用到 Android 平臺,快速構建 Android 應用。ios

若是以前只用過 Objective-C 或者 Swift 寫應用的話,你極可能不會對使用 JavaScript 來編寫應用的願景感到興奮。儘管如此,做爲一個 Swift 開發者來講,上面提到的第二點應該能夠激起你的興趣!git

你經過 Swift,毫無疑問學習到了新的更多有效的編碼方法和技巧,鼓勵轉換和不變性。然而,構建 UI 的方式仍是和使用 Objective-C 的方式一致。仍然以 UIKit 爲基礎,專斷專橫。es6

經過像 virtual DOM 和 reconciliation 這些有趣的概念,React 將函數式編程直接帶到了 UI 層。

這篇教程將帶着你一路構建一個 UK 房產搜索應用:

062e83eb739070f18971e3d7a3f473ff_b.jpg

若是你以前一點 JavaScript 都沒寫過,別擔憂。這篇教程帶着你進行一步一步進行編碼。React 使用 CSS 屬性來定義樣式,通常比較容易讀也比較容易理解。可是若是你想了解更多的話,能夠去看看 Mozilla Developer Network reference,很不錯的。

想要學習更多,繼續往下讀!

準備工做

React Native 框架託管在 GitHub 上。你能夠經過兩種方式獲取到它:使用 git 克隆倉庫,或者下載一個 zip 壓縮包文件。若是你的機器上已經安裝了 React Native,在着手編碼前還有其餘幾個因素須要考慮。

  • React Native 藉助 Node.js,即 JavaScript 運行時來建立 JavaScript 代碼。若是你已經安裝了 Node.js,那就能夠上手了。

首先,使用 Homebrew 官網提供的指引安裝 Homebrew,而後在終端執行如下命令:

1
brew install node

接下來,使用 homebrew 安裝 watchman,一個來自Facebook 的觀察程序:

1
brew install watchman

經過配置 watchman,React 實現了在代碼發生變化時,完成相關的重建的功能。就像在使用 Xcode 時,每次保存文件都會進行一次建立。

React Native 有不少的依賴,須要在運行以前安裝好。在 React Native 文件目錄下打開一個終端,執行下面代碼:

1
npm install

這裏經過 Node 包管理器抓取到項目的全部依賴;功能上和 CocoaPods 或者 Carthage 相似。成功執行該命令後,你會發現一個 node_modules 文件夾被建立,包含了各類外部依賴。

最後,啓動開發服務器。在剛纔打開的終端中,執行下面命令:

1
npm start

abe1dbe5bf178ec5db1d7098387675ee_b.jpg

執行上面命令,你會看到:

45.png

就這樣簡單,準備開始!腳本在終端繼續執行,咱們繼續。

至此,我推薦嘗試一個 React Native 示例來測試配置項。在 react-native/Examples/Movies 文件夾下打開項目,而後建立而且運行它,確保你能夠正確地發佈這個 Movies 應用。

注意:在進入編碼工做以前,還有最後一件事 —— 在這個教程中,你須要編寫大量的 JavaScript 代碼,Xcode 並不是是最好的工具!我使用 Sublime Text,一個價格合理且應用普遍的編輯器。不過,atombrackets 或者其餘輕量的編輯器都能勝任這份工做。

React Native 你好

在開始「搜房App」以前,先來個簡單的 Hello World App 熱熱身。在這一節裏,你將會使用到一些組件。

下載起始項目,解壓縮到react-native/Examples目錄中。解壓完成後,在Xcode中打開 PropertyFinder 項目,不要直接運行這個項目,還須要加上一些JS!

在編輯器中打開 PropertyFinderApp.js,將下面這行代碼加到文件的開頭位置:

1
'use strict' ;

這行代碼是用於開啓 Strict Mode,Strict mode的錯誤處理能夠有所提升,JavaScript的一些語言缺陷也能夠避免。簡而言之就是,JavaScript在這種模式下工做地更好!

注意:想要研究一下 Strict Mode 的朋友,我會推薦你閱讀 Jon Resig 的文章:「ECMAScript 5 Strict Mode, JSON, and More

而後,加上這一行:

1
var  React = require( 'react-native' );

這句代碼是將 react-native 模塊加載進來,並將它賦值給變量 React 的。React Native 使用同 Node.js 相同的模塊加載方式:require,這個概念能夠等同於 Swift 中的「連接庫」或者「導入庫」。

注意:想要了解更多關於 JavaScript 模塊的知識,我推薦閱讀 Addy Osmani 寫的這篇文章

在 require 語句的下面,加上這一段:

1
2
3
4
5
6
7
8
var  styles = React.StyleSheet.create({
   text: {
     color:  'black' ,
     backgroundColor:  'white' ,
     fontSize: 30,
     margin: 80
   }
});

以上代碼定義了一段應用在 「Hello World」 文本上的樣式。若是你曾接觸過Web開發,那你極可能已經發現了:React Native 使用的是 CSS 來定義應用界面的樣式。

如今咱們來關注應用自己吧!依然是在相同的文件下,將如下代碼添加到樣式代碼的下面:

1
2
3
4
5
class PropertyFinderApp extends React.Component {
   render() {
     return  React.createElement(React.Text, {style: styles.text},  "Hello World!" );
   }
}

是的,就是 JavaScript class!

類 (class) 是在ES6中被引入的,縱然JavaScript一直在進步,但Web開發者受困於兼容瀏覽器的情況中,不能怎麼使用JS的新特性。React Native運行在JavaScriptCore中是,也就是說,你可使用JS的新特性啦,徹底不用擔憂兼容什麼的呢。

注意:若是你是一名 Web 開發者,我百分百鼓勵你要使用現代的JavaScript,而後使用像 Babel 這樣的工具生成兼容性的 JavaScript,用於支持兼容性很差的老瀏覽器。

PropertyFinderApp 繼承了 React.Component(React UI的基礎模塊)。組件包含着不可變的屬性,可變的狀態變量以及暴露給渲染用的方法。這會你作的應用比較簡單,只用一個渲染方法就能夠啦。

React Native 組件並非 UIKit 類,它們只能說是在某種程度上等同。框架只是將 React 組件樹轉化成爲原生的UI。

最後一步啦,將這一行加在文件末尾:

1
React.AppRegistry.registerComponent( 'PropertyFinderApp' function () {  return  PropertyFinderApp });

AppRegistry 定義了App的入口,並提供了根組件。

保存 PropertyFinderApp.js,回到Xcode中。確保 PropertyFinder 規劃(scheme)已經勾選了,並設置了相應的 iPhone 模擬器,而後生成並運行你的項目。幾秒以後,你應該就能夠看到 「Hello World」 應用正在運行了:

80b700261430c5da1da10c9034afbc6b_b.jpg

這個JavaScript應用運行在模擬器上,使用的是原生UI,沒有任何內嵌的瀏覽器哦!

還不相信這是真的?:] 那打開你的 Xcode,選擇 Debug\View Debugging\Capture View Hierarchy,你看 native view hierarchy 中都沒有 UIWebView,就只有一個原生的view!:]

你必定很好奇其中的原理吧,那就在 Xcode 中打開 AppDelegate.m,接着找到 application:didFinishLaunchingWithOptions:這個方法構建了 RCTRootView 用於加載 JavaScript 應用以及渲染最後的視圖的。

當應用開始運行的時候,RCTRootView將會從如下的URL中加載應用:

1
http: //localhost:8081/Examples/PropertyFinder/PropertyFinderApp.includeRequire.runModule.bundle

從新調用了你在運行這個App時打開的終端窗口,它開啓了一個 packager 和 server 來處理上面的請求。

在 Safari 中打開那個 URL;你將會看到這個 App 的 JavaScript 代碼。你也能夠在 React Native 框架中找到你的 「Hello World」 代碼。

當你的App開始運行了之後,這段代碼將會被加載進來,而後 JavaScriptCore 框架將會執行它。在 Hello World 的例子裏,它將會加載 PropertyFinderApp 組件,而後構建出原生的 UIKit 視圖。關於這部分的內容,後文裏會再詳細解釋的。

你好 JSX 的世界

你當前的應用程序會使用 React.createElement 來構建應用 UI ,React會將其轉換到原生環境中。在當前狀況下,你的JavaScript代碼是徹底可讀的,但一個更復雜的 UI 與嵌套的元素將迅速使代碼變成一大坨。 

確保應用程序仍在運行,而後回到你的文本編輯器中,編輯 PropertyFinderApp.js 。修改組件 render 方法的返回語句以下:

1
return  Hello World (Again);

這是 JSX ,或 JavaScript 語法擴展,它直接在你的 JavaScript 代碼中混合了相似 HTML 的語法;若是你是一個 web 開發人員,應該對此不陌生。在本篇文章中你將一直使用 JSX 。

把你的改動保存到 PropertyFinderApp.js 中,並返回到模擬器。按下 Cmd + R ,你將看到你的應用程序刷新,並顯示更新的消息 「Hello World(again)」。

從新運行一個 React Native 應用程序像刷新 web 瀏覽器同樣簡單!:]

由於你會使用相同的一系列 JavaScript 文件,您可讓應用程序一直運行,只在更改和保存 PropertyFinderApp.js 後刷新便可

注意:若是你感到好奇,能夠看看你的「包」在瀏覽器中,JSX被轉換成什麼。

這個 「Hello World」 已經夠你們玩耍了,是時候構建實際的應用程序了!

添加導航

咱們的房產查找應用使用標準的棧式導航,基於 UIKit 的 navigation controller。如今正是添加的時候。

在 index.ios.js 文件中,把 PropertyFinderApp 重命名爲HelloWorld:

1
class HelloWorld extends React.Component {

「Hello World」 這幾個字你還須要讓它顯示一下子,但它再也不是應用的根組件了。

接下來,在 HelloWorld 這個組件下面添加以下這個類:

1
2
3
4
5
6
class PropertyFinderApp extends React.Component {
   render() {
     return  (
          );
   }
}

構造一個 navigation controller,應用一個樣式,並把初始路由設爲 Hello World 組件。在 Web 開發中,路由就是一種定義應用導航的一種技術,即定義頁面——或者說是路由——與 URL 的對應關係。

在同一個文件中,更新樣式定義,包含以下 container 的樣式:

1
2
3
4
5
6
7
8
9
10
11
var  styles = React.StyleSheet.create({
   text: {
     color:  'black' ,
     backgroundColor:  'white' ,
     fontSize: 30,
     margin: 80
   },
   container: {
     flex: 1
   }
});

在隨後的教程中會告訴你 flex: 1 是什麼意思。

回到模擬器,Cmd+R,看看新 UI 的樣子:

5a8cd3dc5eaee2cbf0c91c3b939044a4_b.jpg

這就是包含了 root view 的 navigation controller,目前 root view 就是 「Hello World」。很棒——應用已經有了基礎的導航結構,到添加真實 UI 的時候了。

建立搜索頁

在項目中添加一個新文件,命名爲 SearchPage.js,而後將其放在PropertyFinderApp.js 所在目錄下。在文件中添加下面代碼:

1
2
3
4
5
6
7
8
9
10
11
12
'use strict' ;
var  React = require( 'react-native' );
var  {
   StyleSheet,
   Text,
   TextInput,
   View,
   TouchableHighlight,
   ActivityIndicatorIOS,
   Image,
   Component
} = React;

你會注意到,位於引入 react-native 所在位置的前面有一個嚴格模式標識,緊接着的聲明語句是新知識。

這是一種解構賦值,准許你獲取對象的多個屬性而且使用一條語句將它們賦給多個變量。結果是,後面的代碼中能夠省略掉 React 前綴;好比,你能夠直接引用 StyleSheet ,而再也不須要 React.StyleSheet。解構一樣適用於操做數組,更多細節請戳這裏

繼續在 SearchPage.js 文件中添加下面的樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
var  styles = StyleSheet.create({
   description: {
     marginBottom: 20,
     fontSize: 18,
     textAlign:  'center' ,
     color:  '#656565'
   },
   container: {
     padding: 30,
     marginTop: 65,
     alignItems:  'center'
   }
});

一樣,以上都是標準的 CSS 屬性。和 Interface Builder 相比,這樣設置樣式缺乏了可視化,可是比起在 viewDidLoad() 中逐個設置視圖屬性的作法更友好!

只須要把組件添加到樣式聲明的前面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SearchPage extends Component {
   render() {
     return  (
      
        
           Search  for  houses to buy!
        
        
           Search by place-name, postcode or search near your location.
        
      
     );
   }
}

render 很好地展現出 JSX 以及它表示的結構。經過這個樣式,你能夠輕易地描繪出組件 UI 的結構:一個容器,包含兩個 text 標籤。

最後,將下面的代碼添加到文件末尾:

1
module.exports = SearchPage;

這能夠 export SearchPage 類,方便在其餘文件中使用它。

下一步是更新應用的路由,以初始化路由。

打開 PropertyFinderApp.js,在文件頂部緊接着上一個 require 語句的位置添加下面代碼:

1
var  SearchPage = require( './SearchPage' );

在 PropertyFinderApp 類的 render 函數內部,經過更新 initialRoute 來引用最新添加的頁面,以下:

1
component: SearchPage

此時,若是你願意則能夠移除 HelloWorld 類以及與它相關聯的樣式。你不在須要那段代碼了。

切換到模擬器,按下 Cmd+R 查看新的 UI:

cbe4401c09d061f7755ef80a353933b9_b.jpg

使用 Flexbox 定義外觀

如今,你已經看到了用基本的 CSS 屬性來控制外間距(margin),內間距(padding)還有顏色(color)。不過,可能你還不太瞭解要如何使用伸縮盒(flexbox),flexbox 是最近新加入 CSS 規範,用它就能很便利地佈局界面。

React Native 用 css-layout(這是一個用 JavaScript 實現flexbox標準而後編譯成 C(iOS平臺)或者Java(Android平臺)的庫)。

Facebook把這個項目單獨出來實在太正確了,這樣能夠編譯成多種語言,促進更多新穎的應用的發展,好比flexbox layout to SVG

在你的App中,容器(container)默認地是縱向佈局,也就是說在它的子元素將會豎直地排列,像這樣: 

e07a20540d130f08167c93d1c6f0f540_b.jpg

這被稱爲主軸 (main axis),它的方向能夠是豎直的也能夠是水平的。

每個子元素在豎直方向上的位置是由它的margin,height和padding共同決定的。容器的 alignItems 屬性也要設置成 center,這個屬性能夠控制子元素在十字軸上的位置。在這裏,它實現了居中對齊的文本。

好啦,如今咱們把輸入框和按鈕加上去吧。打開 SearchPage.js,將下面的代碼插入第二個 Text 元素的後面:

 

1
         Go    Location

如今你已經加上了兩個最高等級的視圖(top-level view),一個視圖包含了文本輸入框和一個按鈕,還有一個視圖內只有一個按鈕。在後文中你會看到,它們的樣式是什麼樣的。

接着,添加上對應的樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
flowRight: {
   flexDirection:  'row' ,
   alignItems:  'center' ,
   alignSelf:  'stretch'
},
buttonText: {
   fontSize: 18,
   color:  'white' ,
   alignSelf:  'center'
},
button: {
   height: 36,
   flex: 1,
   flexDirection:  'row' ,
   backgroundColor:  '#48BBEC' ,
   borderColor:  '#48BBEC' ,
   borderWidth: 1,
   borderRadius: 8,
   marginBottom: 10,
   alignSelf:  'stretch' ,
   justifyContent:  'center'
},
searchInput: {
   height: 36,
   padding: 4,
   marginRight: 5,
   flex: 4,
   fontSize: 18,
   borderWidth: 1,
   borderColor:  '#48BBEC' ,
   borderRadius: 8,
   color:  '#48BBEC'
}

要注意格式問題:每個樣式都是用逗號分隔開的,因此別忘了在container 選擇器後面還要加上一個逗號。

以上的樣式將會應用在你剛剛加上的輸入框和按鈕上。

如今返回到模擬器,而後按下 Cmd+R 刷新界面: 

10077dcc4cdc5d66f87e572203e96fc2_b.jpg

文本區域和 ’Go’ 按鈕在同一行,不須要顯式地定義兩個組件的寬度,你只須要將它們放在同一個容器中,加上 flexDirection:'row' 樣式,再定義好它們的 flex 值。文本區域是 flex:4,按鈕則是 flex:1,這說明二者的寬度比是4:1。

大概你也發現了,你的「按鈕」其實並非按鈕!:] 使用了 UIKit 後,按鈕更傾向因而能夠輕碰(tap)的標籤(label),因此 React Native 團隊決定直接在 JavaScript 中構建按鈕了。因此你在 App 中使用的按鈕是 TouchableHighlight,這是一個 React Native 組件,當輕碰 TouchableHighlight 時,它會變得透明從而顯示出襯底的顏色(也就是按鈕下層的組件顏色)。

搜索界面的最後一步就是加上一張圖。你能夠從這裏下載咱們用的圖片素材並解壓。

在Xcode中打開Images.xcassets文件,點擊加號添加一個新的圖片集。而後將圖片素材拖進正確的「區間」: 

7fd915417400b22cde8a274924fa7808_b.jpg

你須要重啓應用才能讓圖片生效。

將如下代碼添加到 TouchableHighlight 組件後面,它將用於「獲取位置」按鈕:

1
<br>

如今再樣式表的最後加上圖片對應的樣式,別忘了給原樣式中最後一個加上逗號哦:

1
2
3
4
image: {
   width: 217,
   height: 138
}

require('image!house') 語句用於肯定在你應用的asset目錄下的圖片資源,在 Xcode 中,若是你的打開了Images.xcassets,你會看到一個「房屋」的圖標,正是上面代碼中引用到的。

返回到模擬器,Cmd+R刷新UI: 

73421776c82a309841db13177bfa261d_b.jpg

注意:若是你這會沒有看到「房屋」圖片,取而代之的是一張「找不到資源」的圖片,嘗試重啓packager(也就是在終端裏輸入 npm start 命令)。

如今你的應用看起來挺不錯的啦,不過它還少了點功能。接下來你的任務就是給它加上點狀態,讓它執行一些操做。

添加組件狀態

每一個 React 組件都帶有一個key-value存儲的狀態對象,你必須在組件渲染以前設置其初始狀態。

在 SearchPage.js 中,咱們對 SearchPage 類中,render方法前添加如下的代碼。

1
2
3
4
5
6
constructor(props) {
   super (props);
   this .state = {
     searchString:  'london'
   };
}

如今你的組件擁有一個狀態變量:searchString ,且初始值被設置爲 london 。

這時候你須要利用起組件中的狀態了。在render方法中,用如下的代碼替換TextInput元素中的內容:

1
<br>

這一步設置了 TextInput 組件 value 屬性的值,這個值用於把狀態變量 searchString 的當前值做爲展現給用戶的文字。咱們已經考慮初始值的設定了,但若是用戶編輯這裏的文字會發生什麼呢?

第一步須要創建一個方法來處理事件。在 SearchPage 類中添加如下的代碼:

1
2
3
4
5
onSearchTextChanged(event) {
   console.log( 'onSearchTextChanged' );
   this .setState({ searchString: event.nativeEvent.text });
   console.log( this .state.searchString);
}

上面的代碼從 events 中取出了 text 屬性的值,並用於更新組件的狀態。這裏面也添加了一些有用的調試代碼。

當文字改變時,須要讓這個方法被調用,調用後的文字會經過 render 函數返回到組件中。所以咱們須要在標籤上添加一個 onChange 屬性,添加後的標籤以下所示:

1
<br>

當用戶更改文本時,會調用 onChange 上 的函數;在本例中,則是 onSearchTextChanged 。

注意:你估計會對 bind(this) 語句有疑問。在 JavaScript 中,this 這個關鍵字有點不一樣於大多數其餘語言;在 Swift 表示 「自身」。在這種狀況中,bind 能夠確保在 onSearchTextChanged 方法中, this 能夠做爲組件實例的引用。有關更多信息,請參見MDN this頁面。

在你再次刷新你的應用程序以前,還有一個步驟:在 return 前添加如下語句,打印一條日誌來記錄 render() 函數的調用:

1
  console.log( 'SearchPage.render' );

你會從這些日誌語句中學到一些頗有趣的東西!:]

回到你的模擬器,而後按Cmd + R。您如今應該看到文本輸入的初始值爲 「london」 ,編輯一下文本,從而在 Xcode 控制檯中產生一些日誌: 

6656e0f8e9517685bffc220e2ca35e58_b.jpg

注意看上面的截圖,日誌打印的順序看起來有些奇怪:

第一次調用 render() 函數用於設置視圖。當文本變化時, onSearchTextChanged 函數被調用。以後,經過更新組件的狀態來反映輸入了新的文本,這會觸發另外一次 render 。 onSearchTextChanged() 函數也會被調用,會將改變的字符串打印出來。每當應用程序更新任何 React 組件,將會觸發整個UI層的從新繪製,這會調用你全部組件的 render 方法。這是一個好主意,由於這樣作把組件的渲染邏輯,從狀態變化影響UI這一過程當中徹底解耦出來。

與其餘大多數 UI 框架所不一樣的是,你既不須要在狀態改變的時候去手動更新 UI ,或使用某種類型的綁定框架,來建立某種應用程序狀態和它的 UI 表現的關聯;例如,個人文章中講的,經過ReactiveCocoa實現MVVM模式

在 React 中,你再也不須要擔憂 UI 的哪些部分可能受到狀態變化的影響;你的整個應用程序的 UI,均可以簡單地表示爲一個函數的狀態。

此時,你可能已經發現了這一律念中一個根本性的缺陷。是的,很是準確——性能!

你確定不能在 UI 變化時,徹底拋棄掉整個 UI 而後從新繪製吧。

這就是 React 高明的地方了。每當 UI 渲染出來後,render 方法會返回一顆視圖渲染樹,並與當前的 UIKit 視圖進行比較。這個稱之爲 reconciliation 的過程的輸出是一個簡單的更新列表, React 會將這個列表應用到當前視圖。這意味着,只有實際改變了的部分纔會從新繪製。

這個使人拍案叫絕的嶄新概念讓ReactJS變得獨特——virtual-DOM(文檔對象模型,一個web文檔的視圖樹)和 reconciliation 這些概念——被應用於iOS應用程序。

稍後你能夠整理下思路,以後,在剛纔的應用中你仍然有一些工做要作。日誌代碼增長了代碼的繁瑣性,已經不須要了,因此刪除掉日誌代碼。

初始化搜索功能

爲了實現搜索功能,你須要處理 「Go」 按鈕的點擊事件,調用對應的 API,並提供一個視覺效果,告訴用戶正在作查詢。

在 SearchPage.js 中,在構造函數中把初始狀態更新成:

1
2
3
4
this .state = {
   searchString:  'london' ,
   isLoading:  false
};

新的 isLoading 屬性將會記錄是否有請求正在處理的狀態。

在 render 開始的添加以下邏輯:

1
2
3
var  spinner =  this .state.isLoading ?
   (  ) :
   ( );

這是一個三元操做符,與 if 語句相似,即根據組件 isLoading 的狀態,要麼添加一個 indicator,要麼添加一個空的 view。由於整個組件會不停地更新,因此你自由地混合 JSX 和 JavaSript 代碼。

回到用 JSX 定義搜索界面的地方,在圖片的下面添加:

1
{spinner}

給渲染「Go」的 TouchableHighlight 標記添加以下的屬性:

1
onPress={ this .onSearchPressed.bind( this )}

接下來,添加下面這兩個方法到 SearchPage 類中:

1
2
3
4
5
6
7
8
_executeQuery(query) {
   console.log(query);
   this .setState({ isLoading:  true  });
}
onSearchPressed() {
   var  query = urlForQueryAndPage( 'place_name' this .state.searchString, 1);
   this ._executeQuery(query);
}

_executeQuery() 以後會進行真實的查詢,如今的話就是簡單輸出一條信息到控制檯,而且把 isLoading 設置爲對應的值,這樣 UI 就能夠顯示新的狀態了。

提示:JavaScript 的類並無訪問修飾符,所以沒有 「私有」 的該奶奶。所以經常會發現開發者使用一個下劃線做爲方法的前綴,來講明這些方法是私有方法。

當 「Go」 按鈕被點擊時,onSearchPressed() 將會被調用,開始查詢。

最後,添加下面這個工具函數在定義 SearchPage 類的上面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function  urlForQueryAndPage(key, value, pageNumber) {
   var  data = {
       country:  'uk' ,
       pretty:  '1' ,
       encoding:  'json' ,
       listing_type:  'buy' ,
       action:  'search_listings' ,
       page: pageNumber
   };
   data[key] = value;
   var  querystring = Object.keys(data)
     .map(key => key +  '='  + encodeURIComponent(data[key]))
     .join( '&' );
   return  'http://api.nestoria.co.uk/api?'  + querystring;
};
  • 這個函數並不依賴 SearchPage,所以被定義成了一個獨立的函數,而不是類方法。他首先經過 data 來定義查詢字符串所須要的參數,接着將 data 轉換成須要的字符串格式,name=value 對,使用 & 符號分割。語法 => 是一個箭頭函數,又一個對 JavaScript 語言的擴展,提供了這個便捷的語法來建立一個匿名函數。

回到模擬器,Cmd+R,從新加載應用,點擊 「Go」 按鈕。你能夠看到 activity indicator 顯示出來,再看看 Xcode 的控制檯:

21d006d14798b7e8dc57e63f07d42d65_b.jpg

activity indicator 渲染了,而且做爲請求的 URL 出如今輸出中。把 URL 拷貝到瀏覽器中訪問看看獲得的結果。你會看到大量的 JSON 對象。別擔憂——你不須要理解它們,以後會使用代碼來解析之。

提示:應用使用了 Nestoria 的 API 來作房產的搜索。API 返回的 JSON 數據很是的直白。可是你也能夠看看文檔瞭解更多細節,請求什麼 URL 地址,以及返回數據的格式。

下一步就是從應用中發出請求。

執行 API 請求

仍是 SearchPage.js 文件中,更新構造器中的初始 state 添加一個message 變量:

1
2
3
4
5
this .state = {
   searchString:  'london' ,
   isLoading:  false ,
   message:  ''
};

在 render 內部,將下面的代碼添加到 UI 的底部:

1
{ this .state.message}

你須要使用這個爲用戶展現多種信息。

在 SearchPage 類內部,將如下代碼添加到 _executeQuery() 底部:

1
2
3
4
5
6
7
8
fetch(query)
   .then(response => response.json())
   .then(json =>  this ._handleResponse(json.response))
   . catch (error => 
      this .setState({
       isLoading:  false ,
       message:  'Something bad happened '  + error
    }));

這裏使用了 fetch 函數,它是 Web API 的一部分。和 XMLHttpRequest 相比,它提供了更加先進的 API。異步響應會返回一個 promise,成功的話會轉化 JSON 而且爲它提供了一個你將要添加的方法。

最後一步是將下面的函數添加到 SearchPage:

1
2
3
4
5
6
7
8
_handleResponse(response) {
   this .setState({ isLoading:  false  , message:  ''  });
   if  (response.application_response_code.substr(0, 1) ===  '1' ) {
     console.log( 'Properties found: '  + response.listings.length);
   else  {
     this .setState({ message:  'Location not recognized; please try again.' });
   }
}

若是查詢成功,這個方法會清除掉正在加載標識而且記錄下查詢到屬性的個數。

注意:Nestoria 有不少種返回碼具有潛在的用途。好比,202 和 200 會返回最佳位置列表。當你建立完一個應用,爲何不處理一下這些,能夠爲用戶呈現一個可選列表。

保存項目,而後在模擬器中按下 Cmd+R,嘗試搜索 ‘london’;你會在日誌信息中看到 20 properties were found。而後隨便嘗試搜索一個不存在的位置,好比 ‘narnia’,你會獲得下面的問候語。 

3a4a28712b2169f7eea6dad169b2168e_b.jpg

是時候看一下這20個房屋所對應的真實的地方,好比倫敦!

結果顯示

建立一個新的文件,命名爲 SearchResults.js,而後加上下面這段代碼:

1
2
3
4
5
6
7
8
9
10
11
'use strict' ;
var  React = require( 'react-native' );
var  {
   StyleSheet,
   Image, 
   View,
   TouchableHighlight,
   ListView,
   Text,
   Component
} = React;

你確定注意到啦,這裏用到了 require 語句將 react-native 模塊引入其中,還有一個重構賦值語句。

接着就是加入搜索結果的組件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SearchResults extends Component {
   constructor(props) {
     super (props);
     var  dataSource =  new  ListView.DataSource(
       {rowHasChanged: (r1, r2) => r1.guid !== r2.guid});
     this .state = {
       dataSource: dataSource.cloneWithRows( this .props.listings)
     };
   }
   renderRow(rowData, sectionID, rowID) {
     return  (
                         {rowData.title}                  );
   }
   render() {
     return  (
           );
   }
}

上述的代碼裏用到了一個特定的組件 – ListView – 它能將數據一行行地呈現出來,並放置在一個可滾動的容器內,和 UITableView 很類似。經過 ListView.DataSource 將 ListView 的數據引入,還有一個函數來顯示每一行UI。

在構建數據源的同時,你還須要一個函數用來比較每兩行之間是否重複。 爲了確認列表數據的變化,在 reconciliation 過程當中ListView 就會使用到這個函數。在這個實例中,由 Nestoria API 返回的房屋數據都有一個guid 屬性,它就是用來測試數據變化的。

如今將模塊導出的代碼添加至文件末尾:

1
module.exports = SearchResults;

將下面這段代碼加到 SearchPage.js 較前的位置,不過要在 require 語句的後面哦:

1
var  SearchResults = require( './SearchResults' );

這樣咱們就能在 SearchPage 類中使用剛剛加上的 SearchResults 類。

還要把 _handleResponse 方法中的 console.log 語句改爲下面這樣:

1
2
3
4
5
this .props.navigator.push({
   title:  'Results' ,
   component: SearchResults,
   passProps: {listings: response.listings}
});

SearchResults 組件經過上面的代碼傳入列表裏。在這裏用的是 push方法確保搜索結果所有推動導航棧中,這樣你就能夠經過 ‘Back’ 按鈕返回到根頁面。

回到模擬器,按下 Cmd+R 刷新頁面,而後試試看咱們的搜索。估計你會獲得相似下面這樣的結果:

c23812701c4ff54163cf2978e28ebb50_b.jpg

耶!你的搜索實現了呢,不過這搜索結果頁面的顏值也過低了,不要擔憂,接下來給它化化妝。

可點擊樣式

這些 React Native 的原生代碼如今應該理解起來輕車熟路了,因此本教程將會加快速度。

在 SearchResults.js 中,destructuring 聲明後面添加如下語句來定義樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var  styles = StyleSheet.create({
   thumb: {
     width: 80,
     height: 80,
     marginRight: 10
   },
   textContainer: {
     flex: 1
   },
   separator: {
     height: 1,
     backgroundColor:  '#dddddd'
   },
   price: {
     fontSize: 25,
     fontWeight:  'bold' ,
     color:  '#48BBEC'
   },
   title: {
     fontSize: 20,
     color:  '#656565'
   },
   rowContainer: {
     flexDirection:  'row' ,
     padding: 10
   }
});

這些定義了每一行的樣式。

接下來修改 renderRow() 以下:

1
2
3
4
5
6
7
renderRow(rowData, sectionID, rowID) {
   var  price = rowData.price_formatted.split( ' ' )[0];
   return  (
      this .rowPressed(rowData.guid)}
         underlayColor= '#dddddd' >
                                               £{price}            {rowData.title}                                      );
}

這個操做修改了返回的價格,將已經格式了化的」300000 GBP」中的GBP後綴刪除。而後它經過你已經很熟悉的技術來渲染每一行的 UI 。這一次,經過一個 URL 來提供縮略圖的數據, React Native 負責在主線程以外解碼這些數據。

同時要注意 TouchableHighlight 組件中 onPress屬性後使用的箭頭函數;它用於捕獲每一行的 guid。

最後一步,給類添加一個方法來處理按下操做:

1
2
3
rowPressed(propertyGuid) {
   var  property =  this .props.listings.filter(prop => prop.guid === propertyGuid)[0];
}

該方法經過用戶觸發的屬性來定位。目前該方法沒有作任何事,你能夠稍後處理。如今,是時候欣賞你的大做了。

回到模擬器,按下 Cmd + R 查看結果:

1d29bb1920622180d5ae03c64440b664_b.jpg

看起來好多了——儘管你會懷疑是否任何人都能承受住在倫敦的代價!

是時候嚮應用程序添加最後一個視圖了。

房產詳情視圖

添加一個新的文件 PropertyView.js 到項目中,在文件的頂部添加以下代碼:

1
2
3
4
5
6
7
8
9
'use strict' ;
var  React = require( 'react-native' );
var  {
   StyleSheet,
   Image, 
   View,
   Text,
   Component
} = React;

信手拈來了吧!

接着添加以下樣式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var  styles = StyleSheet.create({
   container: {
     marginTop: 65
   },
   heading: {
     backgroundColor:  '#F8F8F8' ,
   },
   separator: {
     height: 1,
     backgroundColor:  '#DDDDDD'
   },
   image: {
     width: 400,
     height: 300
   },
   price: {
     fontSize: 25,
     fontWeight:  'bold' ,
     margin: 5,
     color:  '#48BBEC'
   },
   title: {
     fontSize: 20,
     margin: 5,
     color:  '#656565'
   },
   description: {
     fontSize: 18,
     margin: 5,
     color:  '#656565'
   }
});

而後加上組件自己:

1
2
3
4
5
6
7
8
9
10
11
12
13
class PropertyView extends Component {
   render() {
     var  property =  this .props.property;
     var  stats = property.bedroom_number +  ' bed '  + property.property_type;
     if  (property.bathroom_number) {
       stats +=  ', '  + property.bathroom_number +  ' '  + (property.bathroom_number > 1
         'bathrooms'  'bathroom' );
     }
     var  price = property.price_formatted.split( ' ' )[0];
     return  (
                                 £{price}          {property.title}                          {stats}        {property.summary}          );
   }
}

render() 前面部分對數據進行了處理,與一般的狀況同樣,API 返回的數據參差不齊,每每有些字段是缺失的。這段代碼經過一些簡單的邏輯,讓數據更加地規整一些。

render 剩餘的部分就很是直接了。它就是一個簡單的這個狀態不可變狀態的函數。

最後在文件的末尾加上以下的 export:

1
module.exports = PropertyView;

返回到 SearchResults.js 文件,在頂部,require React 的下面,添加一個新的 require 語句。

1
var  PropertyView = require( './PropertyView' );

接下來更新 rowPassed(),添加跳轉到新加入的 PropertyView:

1
2
3
4
5
6
7
8
rowPressed(propertyGuid) {
   var  property =  this .props.listings.filter(prop => prop.guid === propertyGuid)[0];
   this .props.navigator.push({
     title:  "Property" ,
     component: PropertyView,
     passProps: {property: property}
   });
}

你知道的:回到模擬器,Cmd + R,一路經過搜索點擊一行到房產詳情界面:

d69b23ae9ede0ceda01a302ce146e091_b.jpg

物廉價美——看上去很不錯哦!

應用即將完成,最後一步是容許用戶搜索附近的房產。

地理位置搜索

在 Xcode 中,打開 Info.plist 添加一個新的 key,在編輯器內部單擊鼠標右鍵而且選擇 Add Row。使用NSLocationWhenInUseUsageDescription 做爲 key 名而且使用下面的值:

1
PropertyFinder would like to use your location to find nearby properties

下面是當你添加了新的 key 後,所獲得的屬性列表:

你將把這個關鍵的細節提示呈現給用戶,方便他們請求訪問當前位置。

打開 SearchPage.js,找到用於渲染 Location 按鈕的TouchableHighlight,而後爲其添加下面的屬性值:

1
onPress={ this .onLocationPressed.bind( this )}

當你用手指輕點這個按鈕,會調用 onLocationPressed —— 接下來會定義這個方法。

將下面的代碼添加到 SearchPage 類中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
onLocationPressed() {
   navigator.geolocation.getCurrentPosition(
     location => {
       var  search = location.coords.latitude +  ','  + location.coords.longitude;
       this .setState({ searchString: search });
       var  query = urlForQueryAndPage( 'centre_point' , search, 1);
       this ._executeQuery(query);
     },
     error => {
       this .setState({
         message:  'There was a problem with obtaining your location: '  + error
       });
     });
}

經過 navigator.geolocation 檢索當前位置;這是一個 Web API 所定義的接口,因此對於每一個在瀏覽器中使用 location 服務的用戶來講這個接口都應該是一致的。React Native 框架藉助原生的 iOS location 服務提供了自身的 API 實現。

若是當前位置很容易獲取到,你將調用第一個箭頭函數;這會向Nestoria 發送一個 query。若是出現錯誤則會獲得一個基本的出錯信息。

由於你已經改變了屬性列表,你須要從新啓動這個應用以看到更改。抱歉,此次不能夠 Cmd+R。請中斷 Xcode 中的應用,而後建立和運行項目。

在使用基於位置的搜索前,你須要指定一個被 Nestoria 數據庫覆蓋的位置。在模擬器菜單中,選擇 Debug\Location\Custom Location … 而後輸入 55.02 維度和 -1.42 經度,這個座標是英格蘭北部的一個景色優美的海邊小鎮,我常常在那給家裏打電話。

55ccd1213bb3d057685335156f7b7ccd_b.jpg

警示:咱們能夠正常地使用位置搜索功能,不過可能有部分同窗不能使用(在訪問時返回 access denied 錯誤)—— 咱們尚不肯定其緣由,多是 React Native 的問題?若是誰遇到了一樣的問題而且已經結果,煩請告訴咱們。這裏沒有倫敦那樣值得炫耀 —— 不過更加經濟!:]

下一步行動?

完成了第一個 React Native 應用呢,恭喜你!你能夠下載本教程的完整代碼,親自來試試看。

若是已經接觸過 Web 開發了,你會發現使用 JavaScript 和 React 來定義與原生 UI 相鏈接的接口和導航是多麼地容易。而若是你曾經開發過原生 App,我相信在使用 React Native 的過程裏你會感覺到它種種好處:快速的應用迭代,JavaScript 的引入以及清晰地使用 CSS 定義樣式。

也許下次作 App 的時候,你能夠試試這個框架?或者說,你依然堅持使用 Swift 或者 Objective-C?不管以後你的選擇是怎麼樣的,我都但願讀完這篇文章的你有所收穫,還能把這些收穫融入到你的項目當中是最好的啦。

若是你對這篇教程有任何疑問,請在這篇文章的討論區留言!

原文:Introducing React Native: Building Apps with JavaScript

相關文章
相關標籤/搜索