數(shù)月前应役,Facebook 對外宣布了正在開發(fā)的 React Native 框架情组,這個框架允許你使用 JavaScript 開發(fā)原生的 iOS 應(yīng)用——就在今天燥筷,Beta 版的倉庫釋出了!
基于PhoneGap使用 JavaScript 和 HTML5 開發(fā) iOS 應(yīng)用已經(jīng)有好幾年了院崇,那 React Native 有什么牛的肆氓?
React Native 真的很牛,讓大家興奮異常的主要原因有兩點:
可以基于 React Native使用 JavaScript 編寫應(yīng)用邏輯底瓣,UI 則可以保持全是原生的谢揪。這樣的話就沒有必要就 HTML5 的 UI 做出常見的妥協(xié);
React 引入了一種與眾不同的捐凭、略顯激進但具備高可用性的方案來構(gòu)建用戶界面拨扶。長話短說,應(yīng)用的 UI 簡單通過一個基于應(yīng)用目前狀態(tài)的函數(shù)來表達茁肠。
React Native 的關(guān)鍵就是患民,以把React編程模式的能力帶到移動開發(fā)來作為主要目標。它的目標不是跨平臺一次編寫到處執(zhí)行垦梆,而是一次學(xué)習(xí)跨平臺開發(fā)匹颤。這個是一個非常大的區(qū)別。這篇教程只介紹 iOS 平臺托猩,不過你一旦掌握了相關(guān)的概念印蓖,就可以應(yīng)用到 Android 平臺,快速構(gòu)建 Android 應(yīng)用站刑。
如果之前只用過 Objective-C 或者 Swift 寫應(yīng)用的話另伍,你很可能不會對使用 JavaScript 來編寫應(yīng)用的愿景感到興奮。盡管如此绞旅,作為一個 Swift 開發(fā)者來說摆尝,上面提到的第二點應(yīng)該可以激起你的興趣!
你通過 Swift因悲,毫無疑問學(xué)習(xí)到了新的更多有效的編碼方法和技巧堕汞,鼓勵轉(zhuǎn)換和不變性。然而晃琳,構(gòu)建 UI 的方式還是和使用 Objective-C 的方式一致讯检。仍然以 UIKit 為基礎(chǔ),獨斷專橫卫旱。
通過像 virtual DOM 和 reconciliation 這些有趣的概念人灼,React 將函數(shù)式編程直接帶到了 UI 層。
這篇教程將帶著你一路構(gòu)建一個 UK 房產(chǎn)搜索應(yīng)用:
如果你之前一點 JavaScript 都沒寫過顾翼,別擔(dān)心投放。這篇教程帶著你進行一步一步進行編碼。React 使用 CSS 屬性來定義樣式适贸,一般比較容易讀也比較容易理解灸芳。但是如果你想了解更多的話涝桅,可以去看看Mozilla Developer Network reference,很不錯的烙样。
想要學(xué)習(xí)更多冯遂,繼續(xù)往下讀!
準備工作
React Native 框架托管在 GitHub 上谒获。你可以通過兩種方式獲取到它:使用 git 克隆倉庫蛤肌,或者下載一個 zip 壓縮包文件。如果你的機器上已經(jīng)安裝了 React Native究反,在著手編碼前還有其他幾個因素需要考慮寻定。
React Native 借助Node.js,即 JavaScript 運行時來創(chuàng)建 JavaScript 代碼精耐。如果你已經(jīng)安裝了 Node.js狼速,那就可以上手了。
首先卦停,使用 Homebrew 官網(wǎng)提供的指引安裝 Homebrew向胡,然后在終端執(zhí)行以下命令:
brewinstallnode
接下來,使用 homebrew 安裝watchman惊完,一個來自Facebook 的觀察程序:
brew install watchman
通過配置 watchman僵芹,React 實現(xiàn)了在代碼發(fā)生變化時,完成相關(guān)的重建的功能小槐。就像在使用 Xcode 時拇派,每次保存文件都會進行一次創(chuàng)建。
接下來使用 `npm` 安裝 React Native CLI 工具:
npminstall-greact-native-cli
這使用 Node 包管理器抓取 CLI 工具凿跳,并且全局安裝件豌;`npm` 在功能上與 CocoaPods 或者 Carthage 類似。
瀏覽到你想要創(chuàng)建 React Native 應(yīng)用的文件夾下控嗜,使用 CLI 工具構(gòu)建項目:
react-nativeinitPropertyFinder
現(xiàn)在茧彤,已經(jīng)創(chuàng)建了一個初始項目,包含了創(chuàng)建和運行一個 React Native 應(yīng)用所需的一切疆栏。
如果仔細觀察了創(chuàng)建的文件夾和文件曾掂,你會發(fā)現(xiàn)一個node_modules文件夾,包含了 React Native 框架壁顶。你也會發(fā)現(xiàn)一個index.ios.js文件珠洗,這是 CLI 工具創(chuàng)建的一個空殼應(yīng)用。最后若专,會出現(xiàn)一個 Xcode 項目文件和一個 iOS 文件夾险污,包含了少量的代碼用來引入bootstrap到你的項目中。
打開 Xcode 項目文件,然后創(chuàng)建并運行蛔糯。模擬器將會啟動并且展示下面的問候語:
你可以能發(fā)現(xiàn),有一個終端窗口彈出窖式,輸出了下面的信息:
$npmstart>react-native@0.1.0start/Users/colineberhardt/Projects/react-native>./packager/packager.sh===============================================================|Runningpackageronport8081.|KeepthispackagerrunningwhiledevelopingonanyJS|projects.Feelfreetoclosethistabandrunyourown|packagerinstanceifyouprefer.||https://github.com/facebook/react-native|===============================================================Reactpackagerready.
這就是 React Native 包蚁飒,在 node 下運行。一會兒你就會知道它是用來干什么的萝喘。
不要關(guān)閉終端窗口淮逻;就然它在后臺運行。如果你不小心關(guān)了阁簸,只需要停下來使用 Xcode 重新運行項目爬早。
注意:在進入編碼工作之前,還有最后一件事 —— 在這個教程中启妹,你需要編寫大量的 JavaScript 代碼筛严,Xcode 并非是最好的工具!我使用Sublime Text饶米,一個價格合理且應(yīng)用廣泛的編輯器桨啃。不過,atom檬输,brackets或者其他輕量的編輯器都能勝任這份工作照瘾。
React Native 你好
在開始“搜房App”之前,先來個簡單的 Hello World App 熱熱身丧慈。在這一節(jié)里析命,你將會使用到一些組件。
在你鐘愛的編輯其中打開 index.ios.js逃默,刪除當前的內(nèi)容鹃愤,因為你要從頭構(gòu)建你自己的應(yīng)用。然后在文件頂部增加下面這樣一行::
'use strict';
這行代碼是用于開啟Strict Mode笑旺,Strict mode的錯誤處理可以有所提高昼浦,JavaScript的一些語言缺陷也可以避免。簡而言之就是筒主,JavaScript在這種模式下工作地更好关噪!
注意:想要研究一下 Strict Mode 的朋友,我會推薦你閱讀Jon Resig 的文章:“ECMAScript 5 Strict Mode, JSON, and More”
然后乌妙,加上這一行:
varReact=require('react-native');
這句代碼是將 react-native 模塊加載進來使兔,并將它賦值給變量 React 的。React Native 使用同 Node.js 相同的模塊加載方式:require藤韵,這個概念可以等同于 Swift 中的“鏈接庫”或者“導(dǎo)入庫”虐沥。
注意:想要了解更多關(guān)于 JavaScript 模塊的知識,我推薦閱讀Addy Osmani 寫的這篇文章。
在 require 語句的下面欲险,加上這一段:
varstyles=React.StyleSheet.create({text:{color:'black',backgroundColor:'white',fontSize:30,margin:80}});
以上代碼定義了一段應(yīng)用在 “Hello World” 文本上的樣式镐依。如果你曾接觸過Web開發(fā),那你很可能已經(jīng)發(fā)現(xiàn)了:React Native 使用的是CSS來定義應(yīng)用界面的樣式天试。
現(xiàn)在我們來關(guān)注應(yīng)用本身吧槐壳!依然是在相同的文件下,將以下代碼添加到樣式代碼的下面:
classPropertyFinderAppextendsReact.Component{render(){returnReact.createElement(React.Text,{style:styles.text},"Hello World!");}}
是的喜每,奏是 JavaScript class务唐!
類 (class) 是在ES6中被引入的,縱然JavaScript一直在進步带兜,但Web開發(fā)者受困于兼容瀏覽器的狀況中枫笛,不能怎么使用JS的新特性。React Native運行在JavaScriptCore中是刚照,也就是說刑巧,你可以使用JS的新特性啦,完全不用擔(dān)心兼容什么的呢涩咖。
注意:如果你是一名 Web 開發(fā)者海诲,我百分百鼓勵你要使用現(xiàn)代的JavaScript,然后使用像Babel這樣的工具生成兼容性的 JavaScript檩互,用于支持兼容性不好的老瀏覽器特幔。
PropertyFinderApp 繼承了 React.Component(React UI的基礎(chǔ)模塊)。組件包含著不可變的屬性闸昨,可變的狀態(tài)變量以及暴露給渲染用的方法蚯斯。這會你做的應(yīng)用比較簡單,只用一個渲染方法就可以啦饵较。
React Native 組件并不是 UIKit 類拍嵌,它們只能說是在某種程度上等同⊙撸框架只是將 React 組件樹轉(zhuǎn)化成為原生的UI横辆。
最后一步啦,將這一行加在文件末尾:
React.AppRegistry.registerComponent('PropertyFinder',function(){returnPropertyFinderApp});
AppRegistry 定義了App的入口茄猫,并提供了根組件狈蚤。
保存PropertyFinderApp.js,回到Xcode中划纽。確保PropertyFinder規(guī)劃(scheme)已經(jīng)勾選了脆侮,并設(shè)置了相應(yīng)的 iPhone 模擬器,然后生成并運行你的項目勇劣。幾秒之后靖避,你應(yīng)該就可以看到 “Hello World” 應(yīng)用正在運行了:
這個JavaScript應(yīng)用運行在模擬器上潭枣,使用的是原生UI,沒有任何內(nèi)嵌的瀏覽器哦幻捏!
還不相信這是真的盆犁?:] 那打開你的 Xcode,選擇Debug\View Debugging\Capture View Hierarchy篡九,你看native view hierarchy中都沒有 UIWebView蚣抗,就只有一個原生的view!:]
你一定很好奇其中的原理吧瓮下,那就在 Xcode 中打開AppDelegate.m,接著找到 application:didFinishLaunchingWithOptions:這個方法構(gòu)建了 RCTRootView 用于加載 JavaScript 應(yīng)用以及渲染最后的視圖的钝域。
當應(yīng)用開始運行的時候讽坏,RCTRootView將會從以下的URL中加載應(yīng)用:
http://localhost:8081/index.ios.bundle
重新調(diào)用了你在運行這個App時打開的終端窗口,它開啟了一個 packager 和 server 來處理上面的請求例证。
在 Safari 中打開那個 URL路呜;你將會看到這個 App 的 JavaScript 代碼。你也可以在 React Native 框架中找到你的 “Hello World” 代碼织咧。
當你的App開始運行了以后胀葱,這段代碼將會被加載進來,然后 JavaScriptCore 框架將會執(zhí)行它笙蒙。在 Hello World 的例子里抵屿,它將會加載 PropertyFinderApp 組件,然后構(gòu)建出原生的 UIKit 視圖捅位。關(guān)于這部分的內(nèi)容轧葛,后文里會再詳細解釋的。
你好 JSX 的世界
你當前的應(yīng)用程序會使用 React.createElement 來構(gòu)建應(yīng)用 UI ,React會將其轉(zhuǎn)換到原生環(huán)境中艇搀。在當前情況下尿扯,你的JavaScript代碼是完全可讀的,但一個更復(fù)雜的 UI 與嵌套的元素將迅速使代碼變成一大坨。
確保應(yīng)用程序仍在運行,然后回到你的文本編輯器中焰雕,編輯 PropertyFinderApp.js 衷笋。修改組件 render 方法的返回語句如下:
returnHelloWorld(Again);
這是 JSX ,或 JavaScript 語法擴展,它直接在你的 JavaScript 代碼中混合了類似 HTML 的語法;如果你是一個 web 開發(fā)人員,應(yīng)該對此不陌生矩屁。在本篇文章中你將一直使用 JSX 辟宗。
把你的改動保存到 PropertyFinderApp.js 中,并返回到模擬器档插。按下 Cmd + R ,你將看到你的應(yīng)用程序刷新,并顯示更新的消息 “Hello World(again)”慢蜓。
重新運行一個 React Native 應(yīng)用程序像刷新 web 瀏覽器一樣簡單!:]
因為你會使用相同的一系列 JavaScript 文件,您可以讓應(yīng)用程序一直運行,只在更改和保存 PropertyFinderApp.js 后刷新即可
注意:如果你感到好奇,可以看看你的“包”在瀏覽器中,JSX被轉(zhuǎn)換成什么郭膛。
這個 “Hello World” 已經(jīng)夠大家玩耍了,是時候構(gòu)建實際的應(yīng)用程序了!
添加導(dǎo)航
我們的房產(chǎn)查找應(yīng)用使用標準的棧式導(dǎo)航晨抡,基于 UIKit 的 navigation controller。現(xiàn)在正是添加的時候。
在 index.ios.js 文件中耘柱,把 PropertyFinderApp 重命名為HelloWorld:
classHelloWorldextendsReact.Component{
“Hello World” 這幾個字你還需要讓它顯示一會兒如捅,但它不再是應(yīng)用的根組件了。
接下來调煎,在 HelloWorld 這個組件下面添加如下這個類:
classPropertyFinderAppextendsReact.Component{render(){return();}}
構(gòu)造一個 navigation controller镜遣,應(yīng)用一個樣式,并把初始路由設(shè)為 Hello World 組件士袄。在 Web 開發(fā)中悲关,路由就是一種定義應(yīng)用導(dǎo)航的一種技術(shù),即定義頁面——或者說是路由——與 URL 的對應(yīng)關(guān)系娄柳。
在同一個文件中寓辱,更新樣式定義,包含如下 container 的樣式:
varstyles=React.StyleSheet.create({text:{color:'black',backgroundColor:'white',fontSize:30,margin:80},container:{flex:1}});
在隨后的教程中會告訴你 flex: 1 是什么意思赤拒。
回到模擬器秫筏,Cmd+R,看看新 UI 的樣子:
這就是包含了 root view 的 navigation controller挎挖,目前 root view 就是 “Hello World”这敬。很棒——應(yīng)用已經(jīng)有了基礎(chǔ)的導(dǎo)航結(jié)構(gòu),到添加真實UI 的時候了蕉朵。
創(chuàng)建搜索頁
在項目中添加一個新文件崔涂,命名為 SearchPage.js,然后將其放在PropertyFinderApp.js 所在目錄下墓造。在文件中添加下面代碼:
'use strict';varReact=require('react-native');var{StyleSheet,Text,TextInput,View,TouchableHighlight,ActivityIndicatorIOS,Image,Component}=React;
你會注意到堪伍,位于引入 react-native 所在位置的前面有一個嚴格模式標識,緊接著的聲明語句是新知識觅闽。
這是一種解構(gòu)賦值帝雇,準許你獲取對象的多個屬性并且使用一條語句將它們賦給多個變量。結(jié)果是蛉拙,后面的代碼中可以省略掉 React 前綴尸闸;比如,你可以直接引用 StyleSheet 孕锄,而不再需要 React.StyleSheet吮廉。解構(gòu)同樣適用于操作數(shù)組,更多細節(jié)請戳這里畸肆。
繼續(xù)在 SearchPage.js 文件中添加下面的樣式:
varstyles=StyleSheet.create({description:{marginBottom:20,fontSize:18,textAlign:'center',color:'#656565'},container:{padding:30,marginTop:65,alignItems:'center'}});
同樣宦芦,以上都是標準的 CSS 屬性。和 Interface Builder 相比轴脐,這樣設(shè)置樣式缺少了可視化调卑,但是比起在 viewDidLoad() 中逐個設(shè)置視圖屬性的做法更友好抡砂!
只需要把組件添加到樣式聲明的前面:
classSearchPageextendsComponent{render(){return(Searchforhousestobuy!Searchbyplace-name,postcodeorsearchnearyourlocation.);}}
render 很好地展示出 JSX 以及它表示的結(jié)構(gòu)。通過這個樣式恬涧,你可以輕易地描繪出組件 UI 的結(jié)構(gòu):一個容器注益,包含兩個 text 標簽。
最后溯捆,將下面的代碼添加到文件末尾:
module.exports=SearchPage;
這可以 export SearchPage 類丑搔,方便在其他文件中使用它。
下一步是更新應(yīng)用的路由提揍,以初始化路由啤月。
打開 PropertyFinderApp.js,在文件頂部緊接著上一個 require 語句的位置添加下面代碼:
varSearchPage=require('./SearchPage');
在 PropertyFinderApp 類的 render 函數(shù)內(nèi)部劳跃,通過更新 initialRoute 來引用最新添加的頁面顽冶,如下:
component:SearchPage
此時,如果你愿意則可以移除 HelloWorld 類以及與它相關(guān)聯(lián)的樣式售碳。你不在需要那段代碼了。
切換到模擬器绞呈,按下 Cmd+R 查看新的 UI:
使用 Flexbox 定義外觀
現(xiàn)在贸人,你已經(jīng)看到了用基本的 CSS 屬性來控制外間距(margin),內(nèi)間距(padding)還有顏色(color)佃声。不過艺智,可能你還不太了解要如何使用伸縮盒(flexbox),flexbox 是最近新加入 CSS 規(guī)范圾亏,用它就能很便利地布局界面十拣。
React Native 用css-layout(這是一個用 JavaScript 實現(xiàn)flexbox標準然后編譯成 C(iOS平臺)或者Java(Android平臺)的庫)。
Facebook把這個項目單獨出來實在太正確了志鹃,這樣可以編譯成多種語言夭问,促進更多新穎的應(yīng)用的發(fā)展,比如flexbox layout to SVG曹铃。
在你的App中缰趋,容器(container)默認地是縱向布局,也就是說在它的子元素將會豎直地排列陕见,像這樣:
這被稱為主軸 (main axis)秘血,它的方向可以是豎直的也可以是水平的。
每一個子元素在豎直方向上的位置是由它的margin评甜,height和padding共同決定的灰粮。容器的 alignItems 屬性也要設(shè)置成 center,這個屬性可以控制子元素在十字軸上的位置忍坷。在這里粘舟,它實現(xiàn)了居中對齊的文本熔脂。
好啦,現(xiàn)在我們把輸入框和按鈕加上去吧蓖乘。打開SearchPage.js锤悄,將下面的代碼插入第二個 Text 元素的后面:
GoLocation
現(xiàn)在你已經(jīng)加上了兩個最高等級的視圖(top-level view),一個視圖包含了文本輸入框和一個按鈕嘉抒,還有一個視圖內(nèi)只有一個按鈕零聚。在后文中你會看到,它們的樣式是什么樣的些侍。
接著隶症,添加上對應(yīng)的樣式:
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 選擇器后面還要加上一個逗號岗宣。
以上的樣式將會應(yīng)用在你剛剛加上的輸入框和按鈕上蚂会。
現(xiàn)在返回到模擬器,然后按下 Cmd+R 刷新界面:
文本區(qū)域和 ’Go’ 按鈕在同一行耗式,不需要顯式地定義兩個組件的寬度胁住,你只需要將它們放在同一個容器中,加上 flexDirection:'row' 樣式刊咳,再定義好它們的 flex 值彪见。文本區(qū)域是 flex:4,按鈕則是 flex:1娱挨,這說明兩者的寬度比是4:1余指。
大概你也發(fā)現(xiàn)了,你的“按鈕”其實并不是按鈕跷坝!:] 使用了 UIKit 后酵镜,按鈕更傾向于是可以輕碰(tap)的標簽(label),所以 React Native 團隊決定直接在 JavaScript 中構(gòu)建按鈕了柴钻。所以你在 App 中使用的按鈕是 TouchableHighlight淮韭,這是一個 React Native 組件,當輕碰 TouchableHighlight 時贴届,它會變得透明從而顯示出襯底的顏色(也就是按鈕下層的組件顏色)缸濒。
搜索界面的最后一步就是加上一張圖片.你可以從這里下載我們用的圖片素材并解壓。
在Xcode中打開Images.xcassets文件粱腻,點擊加號添加一個新的圖片集庇配。然后將圖片素材拖進正確的“區(qū)間”:
你需要重啟應(yīng)用才能讓圖片生效。
將以下代碼添加到 TouchableHighlight 組件后面绍些,它將用于“獲取位置”按鈕:
現(xiàn)在再樣式表的最后加上圖片對應(yīng)的樣式捞慌,別忘了給原樣式中最后一個加上逗號哦:
image:{width:217,height:138}
require('image!house') 語句用于確定在你應(yīng)用的asset目錄下的圖片資源,在 Xcode 中柬批,如果你的打開了Images.xcassets啸澡,你會看到一個“房屋”的圖標袖订,正是上面代碼中引用到的。
返回到模擬器嗅虏,Cmd+R刷新UI:
注意:如果你這會沒有看到“房屋”圖片洛姑,取而代之的是一張“找不到資源”的圖片,嘗試重啟packager(也就是在終端里輸入 npm start 命令)皮服。
現(xiàn)在你的應(yīng)用看起來挺不錯的啦楞艾,不過它還少了點功能。接下來你的任務(wù)就是給它加上點狀態(tài)龄广,讓它執(zhí)行一些操作硫眯。
添加組件狀態(tài)
每個 React 組件都帶有一個key-value存儲的狀態(tài)對象,你必須在組件渲染之前設(shè)置其初始狀態(tài)择同。
在 SearchPage.js 中两入,我們對 SearchPage 類中,render方法前添加以下的代碼敲才。
constructor(props){super(props);this.state={searchString:'london'};}
現(xiàn)在你的組件擁有一個狀態(tài)變量:searchString 裹纳,且初始值被設(shè)置為 london 。
這時候你需要利用起組件中的狀態(tài)了紧武。在render方法中痊夭,用以下的代碼替換TextInput元素中的內(nèi)容:
這一步設(shè)置了 TextInput 組件 value 屬性的值,這個值用于把狀態(tài)變量 searchString 的當前值作為展示給用戶的文字脏里。我們已經(jīng)考慮初始值的設(shè)定了,但如果用戶編輯這里的文字會發(fā)生什么呢虹曙?
第一步需要建立一個方法來處理事件迫横。在 SearchPage 類中添加以下的代碼:
onSearchTextChanged(event){console.log('onSearchTextChanged');this.setState({searchString:event.nativeEvent.text});console.log(this.state.searchString);}
上面的代碼從 events 中取出了 text 屬性的值,并用于更新組件的狀態(tài)酝碳。這里面也添加了一些有用的調(diào)試代碼矾踱。
當文字改變時,需要讓這個方法被調(diào)用疏哗,調(diào)用后的文字會通過 render 函數(shù)返回到組件中呛讲。因此我們需要在標簽上添加一個 onChange 屬性,添加后的標簽如下所示:
當用戶更改文本時,會調(diào)用 onChange 上 的函數(shù);在本例中,則是 onSearchTextChanged 返奉。
注意:你估計會對 bind(this) 語句有疑問贝搁。在 JavaScript 中,this 這個關(guān)鍵字有點不同于大多數(shù)其他語言;在 Swift 表示 “自身”芽偏。在這種情況中雷逆,bind 可以確保在 onSearchTextChanged 方法中, this 可以作為組件實例的引用。有關(guān)更多信息,請參見MDN this頁面污尉。
在你再次刷新你的應(yīng)用程序之前膀哲,還有一個步驟:在 return 前添加以下語句往产,打印一條日志來記錄 render() 函數(shù)的調(diào)用:
console.log('SearchPage.render');
你會從這些日志語句中學(xué)到一些很有趣的東西!:]
回到你的模擬器,然后按Cmd + R。您現(xiàn)在應(yīng)該看到文本輸入的初始值為 “l(fā)ondon” ,編輯一下文本某宪,從而在 Xcode 控制臺中產(chǎn)生一些日志:
注意看上面的截圖仿村,日志打印的順序看起來有些奇怪:
第一次調(diào)用 render() 函數(shù)用于設(shè)置視圖。當文本變化時兴喂, onSearchTextChanged 函數(shù)被調(diào)用蔼囊。之后,通過更新組件的狀態(tài)來反映輸入了新的文本,這會觸發(fā)另一次 render 瞻想。 onSearchTextChanged() 函數(shù)也會被調(diào)用压真,會將改變的字符串打印出來。每當應(yīng)用程序更新任何 React 組件,將會觸發(fā)整個UI層的重新繪制,這會調(diào)用你所有組件的 render 方法蘑险。這是一個好主意,因為這樣做把組件的渲染邏輯滴肿,從狀態(tài)變化影響UI這一過程中完全解耦出來。
與其他大多數(shù) UI 框架所不同的是,你既不需要在狀態(tài)改變的時候去手動更新 UI ,或使用某種類型的綁定框架佃迄,來創(chuàng)建某種應(yīng)用程序狀態(tài)和它的 UI 表現(xiàn)的關(guān)聯(lián);例如,我的文章中講的泼差,通過ReactiveCocoa實現(xiàn)MVVM模式。
在 React 中,你不再需要擔(dān)心 UI 的哪些部分可能受到狀態(tài)變化的影響;你的整個應(yīng)用程序的 UI呵俏,都可以簡單地表示為一個函數(shù)的狀態(tài)堆缘。
此時,你可能已經(jīng)發(fā)現(xiàn)了這一概念中一個根本性的缺陷普碎。是的,非常準確——性能!
你肯定不能在 UI 變化時吼肥,完全拋棄掉整個 UI 然后重新繪制吧
?這就是 React 高明的地方了麻车。每當 UI 渲染出來后,render 方法會返回一顆視圖渲染樹,并與當前的 UIKit 視圖進行比較缀皱。這個稱之為 reconciliation 的過程的輸出是一個簡單的更新列表, React 會將這個列表應(yīng)用到當前視圖。這意味著动猬,只有實際改變了的部分才會重新繪制啤斗。
這個令人拍案叫絕的嶄新概念讓ReactJS變得獨特——virtual-DOM(文檔對象模型,一個web文檔的視圖樹)和 reconciliation 這些概念——被應(yīng)用于iOS應(yīng)用程序。
稍后你可以整理下思路,之后,在剛才的應(yīng)用中你仍然有一些工作要做赁咙。日志代碼增加了代碼的繁瑣性钮莲,已經(jīng)不需要了,所以刪除掉日志代碼彼水。
初始化搜索功能
為了實現(xiàn)搜索功能崔拥,你需要處理 “Go” 按鈕的點擊事件,調(diào)用對應(yīng)的 API凤覆,并提供一個視覺效果握童,告訴用戶正在做查詢。
在 SearchPage.js 中叛赚,在構(gòu)造函數(shù)中把初始狀態(tài)更新成:
this.state={searchString:'london',isLoading:false};
新的 isLoading 屬性將會記錄是否有請求正在處理的狀態(tài)澡绩。
在 render 開始的添加如下邏輯:
varspinner=this.state.isLoading?():();
這是一個三元操作符稽揭,與 if 語句類似,即根據(jù)組件 isLoading 的狀態(tài)肥卡,要么添加一個 indicator溪掀,要么添加一個空的 view。因為整個組件會不停地更新步鉴,所以你自由地混合 JSX 和 JavaSript 代碼揪胃。
回到用 JSX 定義搜索界面的地方,在圖片的下面添加:
{spinner}
給渲染“Go”的 TouchableHighlight 標記添加如下的屬性:
onPress={this.onSearchPressed.bind(this)}
接下來氛琢,添加下面這兩個方法到 SearchPage 類中:
_executeQuery(query){console.log(query);this.setState({isLoading:true});}onSearchPressed(){varquery=urlForQueryAndPage('place_name',this.state.searchString,1);this._executeQuery(query);}
_executeQuery() 之后會進行真實的查詢喊递,現(xiàn)在的話就是簡單輸出一條信息到控制臺,并且把 isLoading 設(shè)置為對應(yīng)的值阳似,這樣 UI 就可以顯示新的狀態(tài)了骚勘。
提示:JavaScript 的類并沒有訪問修飾符材失,因此沒有 “私有” 的該奶奶持钉。因此常常會發(fā)現(xiàn)開發(fā)者使用一個下劃線作為方法的前綴杨耙,來說明這些方法是私有方法拿诸。
當 “Go” 按鈕被點擊時,onSearchPressed() 將會被調(diào)用驴党,開始查詢要门。
最后钦铁,添加下面這個工具函數(shù)在定義 SearchPage 類的上面:
functionurlForQueryAndPage(key,value,pageNumber){vardata={country:'uk',pretty:'1',encoding:'json',listing_type:'buy',action:'search_listings',page:pageNumber};data[key]=value;varquerystring=Object.keys(data).map(key=>key+'='+encodeURIComponent(data[key])).join('&');return'http://api.nestoria.co.uk/api?'+querystring;};
這個函數(shù)并不依賴 SearchPage玲献,因此被定義成了一個獨立的函數(shù)殉疼,而不是類方法。他首先通過 data 來定義查詢字符串所需要的參數(shù)捌年,接著將 data 轉(zhuǎn)換成需要的字符串格式瓢娜,name=value 對,使用 & 符號分割延窜。語法 => 是一個箭頭函數(shù),又一個對 JavaScript 語言的擴展抹锄,提供了這個便捷的語法來創(chuàng)建一個匿名函數(shù)逆瑞。
回到模擬器,Cmd+R伙单,重新加載應(yīng)用获高,點擊 “Go” 按鈕。你可以看到 activity indicator 顯示出來吻育,再看看 Xcode 的控制臺:
activity indicator 渲染了念秧,并且作為請求的 URL 出現(xiàn)在輸出中。把 URL 拷貝到瀏覽器中訪問看看得到的結(jié)果布疼。你會看到大量的 JSON 對象摊趾。別擔(dān)心——你不需要理解它們币狠,之后會使用代碼來解析之。
提示:應(yīng)用使用了Nestoria 的 API 來做房產(chǎn)的搜索砾层。API 返回的 JSON 數(shù)據(jù)非常的直白漩绵。但是你也可以看看文檔了解更多細節(jié),請求什么 URL 地址肛炮,以及返回數(shù)據(jù)的格式止吐。
下一步就是從應(yīng)用中發(fā)出請求。
執(zhí)行 API 請求
還是 SearchPage.js 文件中侨糟,更新構(gòu)造器中的初始 state 添加一個message 變量:
this.state={searchString:'london',isLoading:false,message:''};
在 render 內(nèi)部碍扔,將下面的代碼添加到 UI 的底部:
{this.state.message}
你需要使用這個為用戶展示多種信息。
在 SearchPage 類內(nèi)部秕重,將以下代碼添加到 _executeQuery() 底部:
fetch(query).then(response=>response.json()).then(json=>this._handleResponse(json.response)).catch(error=>this.setState({isLoading:false,message:'Something bad happened '+error}));
這里使用了 fetch 函數(shù)不同,它是Web API 的一部分。和 XMLHttpRequest 相比悲幅,它提供了更加先進的 API套鹅。異步響應(yīng)會返回一個 promise,成功的話會轉(zhuǎn)化 JSON 并且為它提供了一個你將要添加的方法汰具。
最后一步是將下面的函數(shù)添加到 SearchPage:
_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.'});}}
如果查詢成功卓鹿,這個方法會清除掉正在加載標識并且記錄下查詢到屬性的個數(shù)。
注意:Nestoria 有很多種返回碼具備潛在的用途留荔。比如吟孙,202 和 200 會返回最佳位置列表。當你創(chuàng)建完一個應(yīng)用聚蝶,為什么不處理一下這些杰妓,可以為用戶呈現(xiàn)一個可選列表。
保存項目碘勉,然后在模擬器中按下 Cmd+R巷挥,嘗試搜索 ‘london’;你會在日志信息中看到 20 properties were found验靡。然后隨便嘗試搜索一個不存在的位置倍宾,比如 ‘narnia’,你會得到下面的問候語胜嗓。
是時候看一下這20個房屋所對應(yīng)的真實的地方高职,比如倫敦!
結(jié)果顯示
創(chuàng)建一個新的文件辞州,命名為SearchResults.js怔锌,然后加上下面這段代碼:
'use strict';varReact=require('react-native');var{StyleSheet,Image,View,TouchableHighlight,ListView,Text,Component}=React;
你肯定注意到啦,這里用到了 require 語句將 react-native 模塊引入其中,還有一個重構(gòu)賦值語句埃元。
接著就是加入搜索結(jié)果的組件:
classSearchResultsextendsComponent{constructor(props){super(props);vardataSource=newListView.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 – 它能將數(shù)據(jù)一行行地呈現(xiàn)出來涝涤,并放置在一個可滾動的容器內(nèi),和 UITableView 很相似亚情。通過 ListView.DataSource 將 ListView 的數(shù)據(jù)引入妄痪,還有一個函數(shù)來顯示每一行UI。
在構(gòu)建數(shù)據(jù)源的同時楞件,你還需要一個函數(shù)用來比較每兩行之間是否重復(fù)衫生。 為了確認列表數(shù)據(jù)的變化,在 reconciliation 過程中ListView 就會使用到這個函數(shù)土浸。在這個實例中罪针,由 Nestoria API 返回的房屋數(shù)據(jù)都有一個guid 屬性,它就是用來測試數(shù)據(jù)變化的黄伊。
現(xiàn)在將模塊導(dǎo)出的代碼添加至文件末尾:
module.exports=SearchResults;
將下面這段代碼加到SearchPage.js較前的位置泪酱,不過要在 require 語句的后面哦:
varSearchResults=require('./SearchResults');
這樣我們就能在 SearchPage 類中使用剛剛加上的 SearchResults 類。
還要把 _handleResponse 方法中的 console.log 語句改成下面這樣:
this.props.navigator.push({title:'Results',component:SearchResults,passProps:{listings:response.listings}});
SearchResults 組件通過上面的代碼傳入列表里还最。在這里用的是 push方法確保搜索結(jié)果全部推進導(dǎo)航棧中墓阀,這樣你就可以通過 ‘Back’ 按鈕返回到根頁面。
回到模擬器拓轻,按下 Cmd+R 刷新頁面斯撮,然后試試看我們的搜索。估計你會得到類似下面這樣的結(jié)果:
耶扶叉!你的搜索實現(xiàn)了呢勿锅,不過這搜索結(jié)果頁面的顏值也太低了,不要擔(dān)心枣氧,接下來給它化化妝溢十。
可點擊樣式
這些 React Native 的原生代碼現(xiàn)在應(yīng)該理解起來輕車熟路了,所以本教程將會加快速度。
在 SearchResults.js 中达吞,destructuring 聲明后面添加以下語句來定義樣式:
varstyles=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() 如下:
renderRow(rowData,sectionID,rowID){varprice=rowData.price_formatted.split(' ')[0];return(this.rowPressed(rowData.guid)}underlayColor='#dddddd'>£{price}{rowData.title});}
這個操作修改了返回的價格,將已經(jīng)格式了化的”300000 GBP”中的GBP后綴刪除。然后它通過你已經(jīng)很熟悉的技術(shù)來渲染每一行的 UI 酪劫。這一次,通過一個 URL 來提供縮略圖的數(shù)據(jù), React Native 負責(zé)在主線程之外解碼這些數(shù)據(jù)吞鸭。
同時要注意 TouchableHighlight 組件中 onPress屬性后使用的箭頭函數(shù);它用于捕獲每一行的 guid契耿。
最后一步瞒大,給類添加一個方法來處理按下操作:
rowPressed(propertyGuid){varproperty=this.props.listings.filter(prop=>prop.guid===propertyGuid)[0];}
該方法通過用戶觸發(fā)的屬性來定位螃征。目前該方法沒有做任何事搪桂,你可以稍后處理。現(xiàn)在,是時候欣賞你的大作了踢械。
回到模擬器,按下 Cmd + R 查看結(jié)果:
看起來好多了——盡管你會懷疑是否任何人都能承受住在倫敦的代價!
是時候向應(yīng)用程序添加最后一個視圖了酗电。
房產(chǎn)詳情視圖
添加一個新的文件 PropertyView.js 到項目中,在文件的頂部添加如下代碼:
'use strict';varReact=require('react-native');var{StyleSheet,Image,View,Text,Component}=React;
信手拈來了吧内列!
接著添加如下樣式:
varstyles=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'}});
然后加上組件本身:
classPropertyViewextendsComponent{render(){varproperty=this.props.property;varstats=property.bedroom_number+' bed '+property.property_type;if(property.bathroom_number){stats+=', '+property.bathroom_number+' '+(property.bathroom_number>1?'bathrooms':'bathroom');}varprice=property.price_formatted.split(' ')[0];return(£{price}{property.title}{stats}{property.summary});}}
render() 前面部分對數(shù)據(jù)進行了處理撵术,與通常的情況一樣,API 返回的數(shù)據(jù)良莠不齊话瞧,往往有些字段是缺失的嫩与。這段代碼通過一些簡單的邏輯,讓數(shù)據(jù)更加地規(guī)整一些交排。
render 剩余的部分就非常直接了划滋。它就是一個簡單的這個狀態(tài)不可變狀態(tài)的函數(shù)。
最后在文件的末尾加上如下的 export:
module.exports = PropertyView;
返回到 SearchResults.js 文件埃篓,在頂部处坪,require React 的下面,添加一個新的 require 語句架专。
var PropertyView = require('./PropertyView');
接下來更新 rowPassed()同窘,添加跳轉(zhuǎn)到新加入的 PropertyView:
rowPressed(propertyGuid){varproperty=this.props.listings.filter(prop=>prop.guid===propertyGuid)[0];this.props.navigator.push({title:"Property",component:PropertyView,passProps:{property:property}});}
你知道的:回到模擬器,Cmd + R部脚,一路通過搜索點擊一行到房產(chǎn)詳情界面:
物廉價美——看上去很不錯哦想邦!
應(yīng)用即將完成,最后一步是允許用戶搜索附近的房產(chǎn)睛低。
地理位置搜索
在 Xcode 中案狠,打開 Info.plist 添加一個新的 key,在編輯器內(nèi)部單擊鼠標右鍵并且選擇Add Row钱雷。使用NSLocationWhenInUseUsageDescription 作為 key 名并且使用下面的值:
PropertyFinder would like to use your location to find nearby properties
下面是當你添加了新的 key 后骂铁,所得到的屬性列表:
你將把這個關(guān)鍵的細節(jié)提示呈現(xiàn)給用戶,方便他們請求訪問當前位置罩抗。
打開 SearchPage.js拉庵,找到用于渲染 Location 按鈕的TouchableHighlight,然后為其添加下面的屬性值:
onPress={this.onLocationPressed.bind(this)}
當你用手指輕點這個按鈕套蒂,會調(diào)用 onLocationPressed —— 接下來會定義這個方法钞支。
將下面的代碼添加到 SearchPage 類中:
onLocationPressed(){navigator.geolocation.getCurrentPosition(location=>{varsearch=location.coords.latitude+','+location.coords.longitude;this.setState({searchString:search});varquery=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 服務(wù)的用戶來說這個接口都應(yīng)該是一致的烁挟。React Native 框架借助原生的 iOS location 服務(wù)提供了自身的 API 實現(xiàn)。
如果當前位置很容易獲取到骨坑,你將調(diào)用第一個箭頭函數(shù)撼嗓;這會向Nestoria 發(fā)送一個 query柬采。如果出現(xiàn)錯誤則會得到一個基本的出錯信息。
因為你已經(jīng)改變了屬性列表且警,你需要重新啟動這個應(yīng)用以看到更改粉捻。抱歉,這次不可以 Cmd+R斑芜。請中斷 Xcode 中的應(yīng)用肩刃,然后創(chuàng)建和運行項目。
在使用基于位置的搜索前杏头,你需要指定一個被 Nestoria 數(shù)據(jù)庫覆蓋的位置盈包。在模擬器菜單中,選擇 Debug\Location\Custom Location … 然后輸入 55.02 維度和 -1.42 經(jīng)度醇王,這個坐標是英格蘭北部的一個景色優(yōu)美的海邊小鎮(zhèn)续语,我經(jīng)常在那給家里打電話。
警示:我們可以正常地使用位置搜索功能厦画,不過可能有部分同學(xué)不能使用(在訪問時返回 access denied 錯誤)—— 我們尚不確定其原因疮茄,可能是 React Native 的問題?如果誰遇到了同樣的問題并且已經(jīng)結(jié)果根暑,煩請告訴我們力试。這里沒有倫敦那樣值得炫耀 —— 不過更加經(jīng)濟!:]
下一步行動排嫌?
完成了第一個 React Native 應(yīng)用呢畸裳,恭喜你!你可以下載本教程的完整代碼淳地,親自來試試看怖糊。
如果已經(jīng)接觸過 Web 開發(fā)了,你會發(fā)現(xiàn)使用 JavaScript 和 React 來定義與原生 UI 相連接的接口和導(dǎo)航是多么地容易颇象。而如果你曾經(jīng)開發(fā)過原生 App伍伤,我相信在使用 React Native 的過程里你會感受到它種種好處:快速的應(yīng)用迭代,JavaScript 的引入以及清晰地使用 CSS 定義樣式遣钳。
也許下次做 App 的時候扰魂,你可以試試這個框架?或者說蕴茴,你依然堅持使用 Swift 或者 Objective-C劝评?無論之后你的選擇是怎么樣的,我都希望讀完這篇文章的你有所收獲倦淀,還能把這些收獲融入到你的項目當中是最好的啦蒋畜。
如果你對這篇教程有任何疑問,請在這篇文章的討論區(qū)留言撞叽!
原文:Introducing React Native: Building Apps with JavaScript
外刊君推薦