教程

2019-08-14 14:21 更新

序言

這篇教程旨在讓你使用 React Native 快速的開(kāi)發(fā) iOS 和 Android 應(yīng)用。如果你會(huì)想什么是 React Native 并且為什么 Facebook 構(gòu)建了它,這篇 文章 解釋了為什么。

我們期望你有使用 React 來(lái)寫(xiě)應(yīng)用的經(jīng)驗(yàn)。如果沒(méi)有,你可以在 React website 學(xué)到。

安裝

React Native 需要一些在 開(kāi)始 React Native 中闡明的基本的安裝。

在完成了這些依賴項(xiàng)的安裝之后,這里有兩條可以為一個(gè) React Native 項(xiàng)目完全準(zhǔn)備好的命令。

  1. npm install -g react-native-cli

    react-native-cli 是完成剩余安裝的命令行工具。它是通過(guò) npm 安裝的。這將會(huì)在你的終端里面安裝 react-native 這個(gè)命令行,你只需要做一次即可。

  2. react-native init AwesomeProject

    這一條命令獲取 React Native 的源代碼和依賴包,然后在 AwesomeProject/iOS/AwesomeProject.xcodeproj 創(chuàng)建一個(gè)新的 Xcode 項(xiàng)目,并且在 AwesomeProject/android/app 下面創(chuàng)建一個(gè) gradle 項(xiàng)目。

開(kāi)發(fā)

在 iOS 端,現(xiàn)在你可以在 Xcode 里面打開(kāi)這個(gè)新項(xiàng)目 (AwesomeProject/AwesomeProject.xcodeproj),然后使用 ?+R來(lái)簡(jiǎn)單的構(gòu)建和運(yùn)行這個(gè)項(xiàng)目。這樣做也會(huì)開(kāi)啟允許代碼實(shí)時(shí)渲染的 Node 服務(wù)器。有了它你可以通過(guò)在模擬器里面按住?+R 來(lái)看你的更改,而不用在 Xcode 里面重新編譯。

在 Android 端,在 AwesomeProject 里面運(yùn)行 react-native run-android 來(lái)在你的模擬器設(shè)備上面安裝生成的應(yīng)用,并且開(kāi)啟允許代碼實(shí)時(shí)渲染的 Node 服務(wù)器。為了看到你的更改你必須打開(kāi)震動(dòng)菜單(搖動(dòng)你的設(shè)備或者按住設(shè)備上面的菜單按鈕,在模擬器上面按住 F2 或者 Page Up,在 Genymotion 上面按住 ?+M),然后點(diǎn)擊 Reload JS。

在這篇教程里面我們會(huì)開(kāi)發(fā)一個(gè)簡(jiǎn)單版本的電影應(yīng)用,該應(yīng)用可以獲取電影院里面的 25 部電影,并且將它們顯示在 ListView 里面。

Hello World

react-native init 將會(huì)生成和你的工程名字一樣的應(yīng)用,在這個(gè)例子中就是 AwesomeProject。這是一個(gè)簡(jiǎn)單的 hello world 應(yīng)用。在 iOS 上面你可以編輯 index.ios.js 來(lái)給這個(gè)應(yīng)用做一些改變,然后在模擬器里面按住 ?+R 來(lái)看發(fā)生的改變。在 Android 上面可以編輯 index.android.js來(lái)給你的應(yīng)用做一些改變,并且按住震動(dòng)菜單上面的 Reload JS 來(lái)看發(fā)生的改變。

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

在我們書(shū)寫(xiě)代碼來(lái)獲取真正的 Rotten Tomatoes 數(shù)據(jù)之前,我們可以偽造一些數(shù)據(jù)開(kāi)始使用 React Native。在 Facebook 我們經(jīng)常會(huì)在 JS 文件的頭部申明常量,就在 requires 下面,但是你想增加什么數(shù)據(jù)就增加什么數(shù)據(jù)。在index.ios.js 或者 index.android.js 里面:

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

渲染一部電影

我們將要給這部電影渲染標(biāo)題,年份,縮略圖。因?yàn)榭s略圖在 React Native 里面是一個(gè)圖片組件,在下面的 React requires 里面增加 Image。

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

現(xiàn)在我們改變這個(gè)渲染函數(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>
    );
  }

按住 ?+R / Reload JS 然后你就會(huì)看到在 "2015" 上面的 "Title" 。注意 Image 并不會(huì)渲染任何東西。這是因?yàn)槲覀儧](méi)有給我們想要渲染的圖片增加寬度和高度。這將會(huì)由樣式來(lái)完成。讓我們?cè)诟淖儤邮降臅r(shí)候我們可以清除一些我們不再使用的樣式。

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

最后我們需要將這個(gè)樣式應(yīng)用到這個(gè)圖片組件上面:

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

按住 ?+R / Reload JS 現(xiàn)在這個(gè)圖片就會(huì)渲染了。

增加一些樣式

太棒了,我們已經(jīng)渲染了我們的數(shù)據(jù)?,F(xiàn)在讓我們讓它看起來(lái)更美觀一點(diǎn)。我將會(huì)將文字放在圖片的右邊,并且讓標(biāo)題更大,然后在區(qū)域里面居中。

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

我們需要增加另外一個(gè)容器來(lái)垂直的展開(kāi)在水平方向上面展開(kāi)的組件。

      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>
      );

沒(méi)有改變很多,我們?cè)谖谋就饷嬖黾恿艘粋€(gè)容器,然后將它們移動(dòng)到圖片后面(因?yàn)樗鼈冊(cè)趫D片右邊)?,F(xiàn)在讓我們看看樣式都改變了什么:

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

我們使用 FlexBox 來(lái)布局-可以看看 這篇文章 來(lái)了解更多。

在上面的代碼片段里面,我們簡(jiǎn)單的增加了 flexDirection: 'row' ,這將會(huì)讓在主容器里面的孩子節(jié)點(diǎn)水平的展開(kāi)而不是垂直展開(kāi)。

現(xiàn)在我們給這個(gè) JS 的 style 對(duì)象增加另外一個(gè)樣式:

  rightContainer: {
    flex: 1,
  },

這意味著這個(gè) rightContainer 在沒(méi)有被圖片占據(jù)的父容器里面占據(jù)了剩余的空間。如果這不起作用的話,給rightContainer 增加一個(gè) backgroundColor 并且嘗試著移除flex: 1。你將會(huì)看到這將會(huì)導(dǎo)致父容器的大小將會(huì)變?yōu)槟軌蛉菁{孩子視圖的最小大小。

給文本加上樣式就很直接了:

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

然后按住 ?+R / Reload JS 你就會(huì)看到更新后的視圖了。

獲取真實(shí)數(shù)據(jù)

從 Rotten Tomatoes 的 API獲取數(shù)據(jù)并不和學(xué)習(xí) React Native 有任何關(guān)系,因此繼續(xù)學(xué)習(xí)下去吧。

在這個(gè)文件的頂部增加下面的一些常量(通常在 requires 下面)來(lái)創(chuàng)建獲取數(shù)據(jù)的 REQUEST_URL。

/**
 * For quota reasons we replaced the Rotten Tomatoes' API with a sample data of
 * their very own API that lives in React Native's Github repo.
 */var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';

給我們的應(yīng)用增加一些初始化的狀態(tài),因此我們可以檢查 this.state.movies === null 來(lái)看電影數(shù)據(jù)是否被加載。當(dāng)帶有 this.setState({movies: moviesData}) 響應(yīng)返回的時(shí)候我們可以設(shè)置數(shù)據(jù)。就在我們的 React 類里面的渲染函數(shù)上面增加這段代碼:

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

我們想在組件完成加載的時(shí)候關(guān)閉請(qǐng)求。 在組件被加載之后,componentDidMount 是 React 組件里面只會(huì)調(diào)用一次的函數(shù)。

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

現(xiàn)在給我們的主組件增加上面是用到的 fetchData。這個(gè)方法將會(huì)負(fù)責(zé)處理數(shù)據(jù)的獲取。你需要做的就是在解決預(yù)期的問(wèn)題之后調(diào)用 this.setState({movies: data}) 函數(shù)。因?yàn)?React 的工作方式是:setState 會(huì)觸發(fā)一個(gè)從新渲染,之后渲染函數(shù)就會(huì)注意到 this.state.movies 不再為 null。注意我們?cè)谧詈笳{(diào)用 done() -請(qǐng)總是確保調(diào)用 done() 否則任何拋出的錯(cuò)誤信息都會(huì)被隱藏。

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

現(xiàn)在修改這個(gè)渲染函數(shù)來(lái)渲染一個(gè)加載的視圖,如果我們沒(méi)有任何電影數(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)在按住 ?+R / Reload JS 然后直到響應(yīng)返回的時(shí)候你會(huì)看到 "Loading movies..." ,之后它就會(huì)渲染從 Rotten Tomatoes 獲取到的第一部電影。

ListView

現(xiàn)在讓我們來(lái)修改這個(gè)應(yīng)用來(lái)在一個(gè) ListView 組件里面渲染所有的數(shù)據(jù),而不是只是渲染第一部電影。

為什么一個(gè) ListView 比只渲染所有的元素或者將它們放到一個(gè) ScrollView 要好一些?盡管 React 很快,但是渲染一個(gè)不確定列表的元素可能就會(huì)慢。ListView 渲染視圖,因此你只在屏幕上顯示要顯示的視圖,那些已經(jīng)渲染過(guò)但是不在屏幕上顯示的就會(huì)被從原生視圖層移除。

第一件事就是快:在這個(gè)文件頂部增加 ListView 必須項(xiàng)。

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

現(xiàn)在修改渲染函數(shù),因此一旦我們獲取到了數(shù)據(jù),它就會(huì)渲染一個(gè)列表的電影而不只是一部電影。

  render: function() {    if (!this.state.loaded) {      return this.renderLoadingView();
    }    return (      <ListView
        dataSource={this.state.dataSource}
        renderRow={this.renderMovie}
        style={styles.listView}
      />
    );
  },

這個(gè) DataSource 是一個(gè)被 ListView 用來(lái)決定在更新的過(guò)程中哪一行被改變了的接口。

你會(huì)注意到我們從 this.state 來(lái)使用 dataSource。下一步就是給由 getInitialState 返回的對(duì)象增加一個(gè)空的dataSource。既然我們?cè)?nbsp;dataSource 里面存放數(shù)據(jù),我們不應(yīng)該在此使用 this.state.movies 來(lái)保存數(shù)據(jù)兩次。我們可以使用狀態(tài) (this.state.loaded) 的布爾屬性來(lái)判斷獲取數(shù)據(jù)是否完成。

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

這里是更具狀態(tài)更新的修改之后的 fetchData

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

最后我們給 ListView 組件的 styles JS 對(duì)象增加樣式:

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

現(xiàn)在這是最終結(jié)果:

這里仍然有一些工作要做來(lái)讓它稱為一個(gè)功能完全的應(yīng)用,比如:增加導(dǎo)航欄,搜索框,下拉刷新加載等。在 Movies Example 來(lái)看全部的功能。

最終源代碼

/**
 * 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);


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)