*Demo
幾個(gè)月前facebook推出了React Native框架,允許開(kāi)發(fā)著使用javascript代碼來(lái)實(shí)現(xiàn)iOS原生的應(yīng)用,隨后十月份安卓版的也相繼問(wèn)世,從此我們可以?xún)?yōu)雅的Learn once, write anywhere…
早在幾年前開(kāi)發(fā)者就開(kāi)始使用Javascript+html和PhoneGap來(lái)編寫(xiě)各式各樣的app了,開(kāi)發(fā)者可以?xún)?yōu)雅的完成一套js的shell,然后分別在不同的平臺(tái)下進(jìn)行打包,最終生成不同平臺(tái)的app,知識(shí)app的最終的展現(xiàn)形式都是html類(lèi)型的。一度曾經(jīng)出現(xiàn)webapp 是否要取代native ,這么多年過(guò)去,結(jié)果大家也不言而知了。
但是react native的確是一個(gè)很了不起的東西,開(kāi)發(fā)者們都不禁為之歡呼,react native所展現(xiàn)出來(lái)的應(yīng)用實(shí)質(zhì)上是native應(yīng)用,開(kāi)發(fā)者完成同一套js代碼,分別在iOS和安卓平臺(tái)下分別打包最終分別能映射生成分屬不同的安卓原生應(yīng)用與iOS原生應(yīng)用,這個(gè)優(yōu)勢(shì)可能是目前為止被廣大開(kāi)發(fā)最為喜歡的地方,一直以來(lái)web app最為大家所詬病的可能就是html的頁(yè)面永遠(yuǎn)無(wú)法與原生頁(yè)面的體驗(yàn)相比擬。
通過(guò)react native框架,你可以用JavaScript來(lái)編寫(xiě)和運(yùn)行應(yīng)用程序邏輯,而UI卻可以是真正的本地代碼編寫(xiě)的,因此,你完全不需要一個(gè)HTML5編寫(xiě)的UI。
React框架采用了一種新穎的、激進(jìn)的和高度函數(shù)式的方式來(lái)構(gòu)建UI。簡(jiǎn)單說(shuō),應(yīng)用程序的UI可以簡(jiǎn)單地用一個(gè)函數(shù)來(lái)表示應(yīng)用程序當(dāng)前的狀態(tài)
React Native的重點(diǎn)是把React編程模型引進(jìn)到移動(dòng)App的開(kāi)發(fā)中去。它的目的并不是跨平臺(tái),一次編寫(xiě)到處運(yùn)行。它真正的目標(biāo)是“一次學(xué)習(xí)多處編寫(xiě)”。這是一個(gè)重大的區(qū)別。本教程只涉及iOS,但一旦你學(xué)會(huì)了它的思想,你就可以快速將同樣的知識(shí)應(yīng)用到Android App的編寫(xiě)上。
React Native的編寫(xiě)模式更加友好于從事于js的前端開(kāi)發(fā)者,它本身采用了React js的模式,尤其是從事React js的開(kāi)發(fā)人員,只需要熟悉下基本的文檔就能瞬間變成一個(gè)iOS+安卓雙向通吃的移動(dòng)專(zhuān)家,React內(nèi)部引入可一些新的概念,如 DOM和reconciliation,React直接將函數(shù)式編程的理念用到了UI層面。
不過(guò)相對(duì)來(lái)說(shuō),OC的開(kāi)發(fā)人員只要熟悉一下基本demo看上幾個(gè)例子應(yīng)該就不會(huì)有太多問(wèn)題了,如果之前有過(guò)web端開(kāi)發(fā)經(jīng)驗(yàn)的話相信上手會(huì)更快一些。
下面介紹一個(gè)簡(jiǎn)單的demo操作,這個(gè)教程一起帶你去體驗(yàn)一下京東促銷(xiāo)砍啊砍頁(yè)面的OC->React 移植過(guò)程,通過(guò)本教程你就可以了解React Native的一些基本開(kāi)發(fā)流程了。
效果:

如果你之前從未寫(xiě)過(guò)任何 JavaScript ,別擔(dān)心;這篇教程帶著你一點(diǎn)一點(diǎn)編寫(xiě)代碼。React 使用 CSS 屬性來(lái)定義樣式,這些樣式通常都很易于閱讀和理解,但是如果你想進(jìn)一步了解,可以參考:。
要想學(xué)習(xí)更多內(nèi)容,請(qǐng)往下看
React native 關(guān)于環(huán)境搭建問(wèn)題此處就不多說(shuō)了,詳情請(qǐng)見(jiàn)React native基礎(chǔ)教程,此處就從我們已經(jīng)準(zhǔn)備好一切前序工作開(kāi)始,萬(wàn)事具備只欠東風(fēng),下面開(kāi)始:
首先React Native 啟動(dòng)畫(huà)面開(kāi)始,創(chuàng)建helloworld工程,啟動(dòng)畫(huà)面如下:
與此同時(shí)Xcode還會(huì)打開(kāi)一個(gè)終端窗口,并顯示如下信息:

這是React Navtive Packager,它在node容器中運(yùn)行。你待會(huì)就會(huì)發(fā)現(xiàn)它的用處。
千萬(wàn)不要關(guān)閉這個(gè)窗口,讓它一直運(yùn)行在后面。如果你意外關(guān)閉它,可以在Xcode中先停止程序,再重新運(yùn)行程序。
注意:
React Native完成的js完成的代碼其實(shí)是跑在本地的node下面的,從appdelegate里面可以看到React Native工程會(huì)從一個(gè)本機(jī)地址“http://localhost:8081/index.ios.bundle?platform=ios&dev=true”讀取一個(gè)對(duì)應(yīng)的文件,這個(gè)文件中就是系統(tǒng)已經(jīng)自動(dòng)幫你打包壓縮整合過(guò)以后的一個(gè)js 代碼庫(kù),接下來(lái)React Native引擎會(huì)將這個(gè)庫(kù)中的js代碼完全的解析、翻譯成對(duì)應(yīng)的iOS原生內(nèi)容,最終以iOS原生UI的形式渲染到桌面上,這個(gè)就是React Native整個(gè)工作流程。
在開(kāi)始編寫(xiě)這個(gè)demo之前我們先創(chuàng)建一個(gè)簡(jiǎn)單的Hello World項(xiàng)目,用你喜歡的文本編輯器(例如Sublime Text)打開(kāi)index.ios.js ,刪除所有內(nèi)容。然后加入以下語(yǔ)句:
'use strict';var React = require('react-native');var { ApPRegistry, Text, View,} = React;var HelloWorld = React.createClass({ render: function() { return ( <View> <View><Text>你好, React Native</Text></View> </View> ); }});AppRegistry.registerComponent('HelloWorld', () => HelloWorld);好了,“Hello World” 的演示就到此為止;接下來(lái)我們要編寫(xiě)一個(gè)真正的React App了!
這個(gè)demo使用了標(biāo)準(zhǔn)的UIKit中的導(dǎo)航控制器來(lái)提供”棧式導(dǎo)航體驗(yàn)“。接下來(lái)我們就來(lái)實(shí)現(xiàn)這個(gè)功能。
在 index.ios.js, 添加以下代碼:
var Home = require('./cut/Home');var HelloWorld = React.createClass(//{ render: function() //{ return ( <NavigatorIOS initialRoute=//{//{title:'首頁(yè)', component:Home, //}//}//> ); //}//});NavigatorIOS就是React Native中對(duì)應(yīng)的導(dǎo)航視圖,我們?cè)俅螘簳r(shí)可以理解就是iOS中的UINavigationController,我們?cè)诖颂巹?chuàng)建了一個(gè)基于導(dǎo)航的視圖控制器,rootViewController對(duì)應(yīng)的頁(yè)面就是Home。
var cutList = require('./CutList');var Home = React.createClass({ render:function (){ return ( <TouchableHighlight onPress={()=> this.goToNext()}> <View> <Text}>go to cut</Text> </View> </TouchableHighlight> ); }, goToNext:function(){ this.props.navigator.push({ component: cutList, }); },});Home 我們只放了一個(gè)按鈕,按鈕文字“go to cut”,另外添加了一個(gè)點(diǎn)擊觸摸事件,事件相應(yīng)題是goToNext:function(); 在函數(shù)處理事件內(nèi)部,我們只做了頁(yè)面的push跳轉(zhuǎn),目標(biāo)頁(yè)面是cutList頁(yè)面,運(yùn)行效果如下:
輪播圖這個(gè)地方采用了React Native的一個(gè)第三方庫(kù)swiper(偷懶了),
var Swiper = require('react-native-swiper');初始化數(shù)據(jù)var sliderImgs = [ 'http://m.360buyimg.com/mobile/s725x175_jfs/t2332/80/701506039/111191/37a1273/5624850bN2469d61f.jpg', 'http://m.360buyimg.com/mobile/s725x175_jfs/t2401/354/694665708/117887/3a283185/56248ee2N58518e76.jpg', 'http://m.360buyimg.com/mobile/s725x175_jfs/t2506/269/651438394/152836/cf430d42/561f6b3aN80cb83f4.jpg', 'http://m.360buyimg.com/mobilecms/s750x410_jfs/t2326/263/687562306/170970/c3f92c7/5620cbddNaa6a2cda.jpg!q70.jpg', 'http://m.360buyimg.com/mobilecms/s750x410_jfs/t1891/237/637408747/193879/1acee0f7/5620be19N801621e4.jpg!q70.jpg'];//初始化UIrender:function () //{ return ( <View> <View> <Swiper style=//{styles.wrapper//} showsButtons=//{false//} autoplay=//{true//} height=//{150//} showsPagination={true}> <Image style={[styles.slide,]} source=></Image> <Image style={[styles.slide,]} source=></Image> <Image style={[styles.slide,]} source=></Image> <Image style={[styles.slide,]} source=></Image> <Image style={[styles.slide,]} source=></Image> </Swiper> </View> <View style={styles.listViewSuper}> <ListView style={styles.tableStyle} dataSource = {this.state.dataSource} renderRow={this._renderRow.bind(this)} pageSize={5} automaticallyAdjustContentInsets={false}//> </View> </View> ); },再次看到render:function()這個(gè)函數(shù),應(yīng)該沒(méi)那么陌生了吧,暫時(shí)可以理解render相當(dāng)于ViewController中的ViewDidLoad:,我們一般在render里面做一些初始化UI視圖的工作,此處我們初始化了swiper和ListView
swiper
ListView
getInitialState:function(){ var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}); return { dataSource: new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }), loaded:false, currentPage:0, }; }getInitialState:function()類(lèi)似于OC中的init函數(shù),我們一般的習(xí)慣喜歡在init函數(shù)初始化一個(gè)變量等數(shù)據(jù),在React Native依舊是這樣。
//定義request url var urlPath = 'http://ccguo.gitcafe.io/cut.json';var CACHE = [];//componentDidMount:function 系統(tǒng)方法componentDidMount:function(){ this.fetchData(); },//自定義函數(shù)處理網(wǎng)絡(luò)獲取數(shù)據(jù),將數(shù)據(jù)放入全局變量CACHE cache:function(items){ for (var i in items) { CACHE.push(items[i]); } this.setState({ dataSource: this.state.dataSource.cloneWithRows(CACHE), }); },//發(fā)起 網(wǎng)絡(luò)請(qǐng)求,獲得json fetchData:function(){ console.log('hello world'); fetch(urlPath) .then((response) => response.json()) .then((responseText) => { console.log(responseText.cutList); this.cache(responseText.cutList); }) .catch((error) => { console.log(error); }); }這個(gè)過(guò)程模擬了在iOS原生應(yīng)用里面,初始化網(wǎng)絡(luò)request,發(fā)起網(wǎng)絡(luò)請(qǐng)求,得到數(shù)據(jù),解析數(shù)據(jù),然后將數(shù)據(jù)存入list這一些列操作,其實(shí)在js中,js腳本處理json的能力還是很強(qiáng)的,我們?cè)僖膊恍枰馩C中哪些objectForKey:的操作了,我們不需要任何MJExtension、JSONModel、 Mantle等一些潛在的工具了,省去了很多的麻煩,我們直接拿到一個(gè)json對(duì)象,直接對(duì)對(duì)象進(jìn)行操作。
另外React的網(wǎng)絡(luò)請(qǐng)求此處我們只是使用了fetch API
臉譜官方的api(臉譜對(duì)于網(wǎng)絡(luò)請(qǐng)求提供了多種API,如:fetch WebSocket xmlHttpRequest等,具體可參照API)
從代碼上看js的鏈?zhǔn)骄幊虅偪瓷先ビ悬c(diǎn)不太習(xí)慣,不過(guò)整體使用起來(lái)還是比OC中快捷多了,foreach遍歷、消息隊(duì)列進(jìn)出棧,總之腳步里面省去了以往還不得不在意的好多麻煩,其實(shí)這塊相對(duì)swift而言,新的版本中漸漸的已經(jīng)得到了部分提升,不過(guò)還是要感謝臉譜團(tuán)隊(duì),沒(méi)有他們,可能還見(jiàn)識(shí)不到React的強(qiáng)大。
_renderRow:function(data,sectionID,rowID){ return ( <TouchableHighlight onPress={() => this._pressRow(data,rowID)}> <View style=> <View style=> <View style=> <Image style= source=></Image> </View> <View style={styles.row,{flex:3,borderColor:'blue',borderWidth:0.5}}> <Text style=> {rowID+'-'+data.wname} </Text> <Text style=>京東價(jià)318.00</Text> <View style=> <Text style=>已有256人砍價(jià)</Text> <Text style=>馬上砍</Text> </View> </View> </View> <View style={styles.separator} /> </View> </TouchableHighlight> ); }, _pressRow: function(data,rowID) { this.props.navigator.push({ component: detail, passProps: {data: data} }); }在上述初始化ListView UI的時(shí)間,我們指定了renderRow 對(duì)應(yīng)的action事件,此處我們可以直接在_renderRow:function中構(gòu)建自己的cell模版,至于React Native中UI的標(biāo)簽基本用法,大家可以去頭部基礎(chǔ)教程里面找,有點(diǎn)類(lèi)似于html標(biāo)簽,總之我們?cè)赺renderRow:function純碎是構(gòu)造cell的代碼,這個(gè)類(lèi)似于tableViewCell subClass, cell點(diǎn)擊事件我們使用一個(gè)TouchableHighlight來(lái)代替
<TouchableHighlight onPress={() => this._pressRow(data,rowID)}> .... </TouchableHighlight>TouchableHighlight事件處理action同樣是一個(gè)函數(shù)(不解釋?zhuān)赺pressRow事件中我們處理自己的cell點(diǎn)擊跳轉(zhuǎn),順便插一句下一步的操作,_pressRow(data,rowID)是帶有形參的
另外 ListView renderRow 事件的重載函數(shù),形參類(lèi)型這個(gè)具體參照臉譜官方的api
_renderRow:function(data,sectionID,rowID)。
整體運(yùn)行效果如下:
var detail = require('./Detail'); _pressRow: function(data,rowID) { this.props.navigator.push({ component: detail, passProps: {data: data} }); }React在處理事件跳轉(zhuǎn)的時(shí)間,仍舊采用進(jìn)棧出棧的形式,這一點(diǎn)和Apple的理念還是類(lèi)似的。
<Text style={styles.view}>{this.props.data.wname}</Text>到了目標(biāo)頁(yè)面后,我們直接從props容器直接根據(jù)key就能將傳遞的參數(shù)去處,此處我們傳遞參數(shù)的本身是一個(gè)json,我們只是講wname顯示到detail頁(yè)面。
效果如下:

恭喜你,你的第一個(gè)React Native App終于完成了!你可以在GitHub上找到每一個(gè)”可運(yùn)行的“步驟的項(xiàng)目源文件,如果你搞不定的時(shí)候它們會(huì)非常有用的 :]
如果你來(lái)自Web領(lǐng)域,你可能覺(jué)得在代碼中用JS和React框架建立基于本地化UI的App的界面并實(shí)現(xiàn)導(dǎo)航不過(guò)是小菜一碟。但如果你主要開(kāi)發(fā)的是本地App,我希望你能從中感受到React Native的優(yōu)點(diǎn):快速的App迭代,現(xiàn)代JavaScript語(yǔ)法的支持和清晰的CSS樣式規(guī)則。
在你的下一個(gè)App中,你是會(huì)使用這個(gè)框架,還是會(huì)繼續(xù)頑固不化地使用Swift和O-C呢?
無(wú)論你怎么選擇,我都希望你能從本文的介紹中學(xué)習(xí)到一些有趣的新東西,并把其中一些原理應(yīng)用到你的下一個(gè)項(xiàng)目中。
如果你有任何問(wèn)題及建議,請(qǐng)參與到下面的討論中來(lái)!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注