React Native第一課

前言

本篇文章的作用在于幫助你快速上手使用React Native編寫iOS應(yīng)用访递。如果你現(xiàn)在還不太了解React Native是什么以及Facebook為什么要創(chuàng)建React Native,你可以先看看這篇博客

閱讀本文之前辛藻,我們假設(shè)你已經(jīng)有過使用React創(chuàng)建網(wǎng)站的經(jīng)驗。如果你還是一個React新手小腊,那么我們建議你從React的網(wǎng)站開始學(xué)習(xí)伏尼。

設(shè)置

使用React Native開發(fā)iOS應(yīng)用需要OSX系統(tǒng),Xcode,Homebrew介蛉,node萌庆,npm以及watchman,你也可以有選擇的使用Flow币旧。

在安裝完這些依賴項目之后践险,你可以簡單的使用兩行命令來開啟一個React Native項目:

npm install -g react-native-cli

react-native-cli是用來開發(fā)React Native的命令行工具。你需要使用npm來安裝它吹菱。上面這行代碼將會幫助你在terminal中安裝react-native命令捏境。當(dāng)然,你只需要運行一次這行代碼毁葱。

react-native init AwsomeProject

這行代碼可以獲取所有React Native的源碼以及依賴項垫言,同時會創(chuàng)建一個叫做
AwsomeProject/AwsomeProject.xcodeproj的全新Xcode項目。

開發(fā)

現(xiàn)在你可以在Xcode中開發(fā)這個新項目AwsomeProject/AwsomeProject.xcodeproj倾剿,并簡單的使用cmd+R來運行它筷频。運行代碼的同時也會自動開啟一個node服務(wù)器來實現(xiàn)代碼的熱重載。這樣一來你就可以通過cmd+R來查看變化而不需要每次都在Xcode中進行重編譯前痘。

在本文中我們將創(chuàng)建一個簡單的電影應(yīng)用凛捏,這個應(yīng)用將抓取目前正在上映的最新的25部電影,并將它們展示在一個ListView中芹缔。

Hello World

react-native init會復(fù)制Example/SampleProject中的內(nèi)容到你命名的項目中坯癣,在本文中項目名稱為AwsomeProject。這是一個簡單的hello world應(yīng)用最欠。你可以通過編輯index.os.js來改變這個應(yīng)用示罗,然后使用cmd+R在模擬器中查看變化。

偽造數(shù)據(jù)

在我們開始編寫代碼從Rotten Tomatoes網(wǎng)站抓取數(shù)據(jù)之前芝硬,我們先來偽造一些數(shù)據(jù)以便我們可以馬上體驗一下React Native蚜点。在Facebook我們一般會在JS文件的頂部聲明常量,并在后面使用拌阴,但是隨便你加在哪里都好绍绘。在index.ios.js中添加以下代碼:

var MOCKED_MOVIES_DATA = [
  {title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];

渲染一部電影

我們會渲染電影標(biāo)題,年份以及電影海報略縮圖迟赃。由于略縮圖在React Native中是一個Image組件陪拘,我們需要將Imagei到React的依賴項中。

var {
  AppRegistry,
  Image,
  StyleSheet,
  Text,
  View,
} = React;

現(xiàn)在我們修改render函數(shù)以便我們可以將上面渲染上面的數(shù)據(jù)而不僅僅是渲染一個hello world:

render: function() {
    var movie = MOCKED_MOVIES_DATA[0];
    return (
      <View style={styles.container}>
        <Text>{movie.title}</Text>
        <Text>{movie.year}</Text>
        <Image source={{uri: movie.posters.thumbnail}} />
      </View>
    );
  }

按下cmd+R你應(yīng)該在”2015”上面看到”Title”纤壁。注意此時Image什么都不會渲染左刽。這是因為我們還沒有指定想要的寬度和高度。這需要通過styles屬性來設(shè)置摄乒。在我們修改styles的同時我們還需要把那些不再會使用的樣式刪除:

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
});

最后我們需要將樣式運用在Image組件上悠反。

<Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
 />

按下cmd+R你會發(fā)現(xiàn)圖片已經(jīng)渲染出來了残黑。

添加其他樣式

很好,我們現(xiàn)在已經(jīng)把數(shù)據(jù)渲染出來了≌瘢現(xiàn)在我們來讓我們的應(yīng)用變得好看一些梨水。我想把文字放在圖片的右側(cè),同時讓標(biāo)題大一些并居中:

+---------------------------------+
|+-------++----------------------+|
||       ||        Title         ||
|| Image ||                      ||
||       ||        Year          ||
|+-------++----------------------+|
+---------------------------------+

我們會添加另一個container茵臭,這是為了讓我們的組件在外層的組件中垂直居中疫诽。

return (
        <View style={styles.container}>
          <Image
            source={{uri: movie.posters.thumbnail}}
            style={styles.thumbnail}
          />
          <View style={styles.rightContainer}>
            <Text style={styles.title}>{movie.title}</Text>
            <Text style={styles.year}>{movie.year}</Text>
          </View>
        </View>
);

現(xiàn)在并沒有多少變化,我們在文字外層添加了一個包裹容器并將其放在了圖片后面(因為文字要在圖片的右邊)〉┪現(xiàn)在我們來看看樣式會變成什么樣:

container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },

我們在這里使用彈性盒模型來布局奇徒,如果你不熟悉彈性盒模型,可以看看這個教程缨硝。

在上面的代碼中摩钙,我們簡單的添加了flexDirection: 'row'來確保我們的main container是水平布局而不是垂直布局。

現(xiàn)在我們添加另一組樣式:

 rightContainer: {
    flex: 1,
  },

上面代碼的意思是rightContainer會占據(jù)外層容器右邊的空間查辩,左邊則是圖片胖笛。如果沒有看出效果,可以為rightContainer添加一個backgroundColor屬性宜岛,同時移除flex: 1长踊。你會看到外出容器的體積會變得勁量的小來適應(yīng)子容器。

而文本的樣式很直觀:

title: {
    fontSize: 20,
    marginBottom: 8,
    textAlign: 'center',
  },
  year: {
    textAlign: 'center',
  },

繼續(xù)按下cmd+R來查看更新之后的視圖:

抓取真實數(shù)據(jù)

從Rotten Tomatoes的API抓取數(shù)據(jù)和學(xué)習(xí)React Native并沒有多少關(guān)系萍倡,所以你可以風(fēng)輕云淡的跳過這一節(jié)身弊。

將下面的常量放在文件的頂部來創(chuàng)建一個請求數(shù)據(jù)使用的REQUEST_URL:

var API_KEY = '7waqfqbprs7pajbz28mqf6vz';
var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json';
var PAGE_SIZE = 25;
var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE;
var REQUEST_URL = API_URL + PARAMS;

為我們的應(yīng)用添加初始狀態(tài)以便我們可以通過檢查this.state.movies === null來確定電影數(shù)據(jù)有沒有被城管加載。當(dāng)電影數(shù)據(jù)返回時列敲,我們可以通過this.setState({movies: moviesData})來設(shè)置數(shù)據(jù)阱佛。將下面的代碼添加到render函數(shù)之前:

getInitialState: function() {
    return {
      movies: null,
    };
  },

我們想要在組件完成加載后發(fā)送請求, componentDidMount是React組件中的一個函數(shù)酿炸,它只會在組件加載完成之后被調(diào)用一次瘫絮。

componentDidMount: function() {
    this.fetchData();
  },

現(xiàn)在添加組件中會用到的fetchData函數(shù)涨冀。這個方法將負(fù)責(zé)處理數(shù)據(jù)抓取填硕。你需要做的僅僅是在promise完成解析之后調(diào)用this.setState({movies: data}),因為setState會觸發(fā)重新渲染鹿鳖,而此時render函數(shù)會注意到this.state.movies不再是null扁眯。注意我們會在promise鏈的最后調(diào)用done(),一定要確保調(diào)用done(),否則錯誤信息可能會被忽略。

fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          movies: responseData.movies,
        });
      })
      .done();
  },

現(xiàn)在修改render函數(shù)來渲染一個loading視圖翅帜,如果電影數(shù)據(jù)還沒有返回的話姻檀,否則將渲染第一部電影:

 render: function() {
    if (!this.state.movies) {
      return this.renderLoadingView();
    }

    var movie = this.state.movies[0];
    return this.renderMovie(movie);
  },

  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text>
          Loading movies...
        </Text>
      </View>
    );
  },

  renderMovie: function(movie) {
    return (
      <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}>{movie.title}</Text>
          <Text style={styles.year}>{movie.year}</Text>
        </View>
      </View>
    );
  },

現(xiàn)在按下cmd+R,你應(yīng)該已經(jīng)看到了”Loading movies…”涝滴,直到電影數(shù)據(jù)返回绣版,接著頁面就會渲染第一部從Rotten Tomatoes抓回來的電影:

ListView

現(xiàn)在我們來修改應(yīng)用來將所有的數(shù)據(jù)渲染在一個ListView組件種胶台,而不是只渲染一部電影。

為什么使用ListView要比把所有數(shù)據(jù)放在一個ScrollView里面好呢杂抽?雖然React速度很快诈唬,但是渲染一個可能是無限長的列表依然可能很慢。ListView會自動渲染視線之內(nèi)的視圖缩麸,而那些在屏幕之外的視圖會被暫時移除铸磅。

第一件事:在文件的最上方添加ListView

var {
  AppRegistry,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} = React;

現(xiàn)在修改render函數(shù)以便一旦我們的數(shù)據(jù)返回沃恩就可以在一個ListView里面渲染數(shù)據(jù):

render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie}
        style={styles.listView}
      />
    );
  }

DataSource是一個ListView的接口,作用是決定那些行會被改變杭朱。

注意在這里使用dataSource而不是this.state阅仔。下一步我們需要在getInitialState的返回對象上添加一個空的dataSource,我們不能再使用this.state.movies防止數(shù)據(jù)被存儲兩次弧械。我們可以使用state的布爾值屬性this.state.loaded來判斷數(shù)據(jù)抓取是否結(jié)束:

getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },

在這里我們還需要修改fetchData方法來更新state:

fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
          loaded: true,
        });
      })
      .done();
  },

最后八酒,我們在styles中為ListView組件添加樣式:

 listView: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },

下面是最終的效果圖:

接下來我們還可以通過添加導(dǎo)航,搜索刃唐,無線滾動加載等等來完成一個完整的應(yīng)用丘跌。你可以查看[電影示例](Movies Example)來查看完整的代碼。

完整的源碼

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

var React = require('react-native');
var {
  AppRegistry,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} = React;

var API_KEY = '7waqfqbprs7pajbz28mqf6vz';
var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json';
var PAGE_SIZE = 25;
var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE;
var REQUEST_URL = API_URL + PARAMS;

var AwesomeProject = React.createClass({
  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },

  componentDidMount: function() {
    this.fetchData();
  },

  fetchData: function() {
    fetch(REQUEST_URL)
      .then((response) => response.json())
      .then((responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
          loaded: true,
        });
      })
      .done();
  },

  render: function() {
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie}
        style={styles.listView}
      />
    );
  },

  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text>
          Loading movies...
        </Text>
      </View>
    );
  },

  renderMovie: function(movie) {
    return (
      <View style={styles.container}>
        <Image
          source={{uri: movie.posters.thumbnail}}
          style={styles.thumbnail}
        />
        <View style={styles.rightContainer}>
          <Text style={styles.title}>{movie.title}</Text>
          <Text style={styles.year}>{movie.year}</Text>
        </View>
      </View>
    );
  },
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 20,
    marginBottom: 8,
    textAlign: 'center',
  },
  year: {
    textAlign: 'center',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
  listView: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },
});

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

參考:http://html-js.com/article/2783

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唁桩,一起剝皮案震驚了整個濱河市闭树,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荒澡,老刑警劉巖报辱,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異单山,居然都是意外死亡碍现,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門米奸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昼接,“玉大人,你說我怎么就攤上這事悴晰÷” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵铡溪,是天一觀的道長漂辐。 經(jīng)常有香客問我,道長棕硫,這世上最難降的妖魔是什么髓涯? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮哈扮,結(jié)果婚禮上纬纪,老公的妹妹穿的比我還像新娘蚓再。我一直安慰自己,他們只是感情好包各,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布对途。 她就那樣靜靜地躺著,像睡著了一般髓棋。 火紅的嫁衣襯著肌膚如雪实檀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天按声,我揣著相機與錄音膳犹,去河邊找鬼。 笑死签则,一個胖子當(dāng)著我的面吹牛须床,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渐裂,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼豺旬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了柒凉?” 一聲冷哼從身側(cè)響起族阅,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膝捞,沒想到半個月后坦刀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡蔬咬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年鲤遥,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片林艘。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡盖奈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出狐援,到底是詐尸還是另有隱情钢坦,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布咕村,位于F島的核電站场钉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏懈涛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一泳猬、第九天 我趴在偏房一處隱蔽的房頂上張望批钠。 院中可真熱鬧宇植,春花似錦、人聲如沸埋心。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拷呆。三九已至闲坎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間茬斧,已是汗流浹背腰懂。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留项秉,地道東北人绣溜。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像娄蔼,于是被迫代替她去往敵國和親怖喻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容