React Native學習<三> ReactNative入門基礎

<一>編寫Hello World

React Native看起來很像React,只不過其基礎組件是原生組件而非web組件。要理解React Native應用的基本結構,首先需要了解一些基本的React的概念,比如JSX語法、組件、state狀態以及props屬性。如果你已經了解了React,那么還需要掌握一些React Native特有的知識,比如原生組件的使用。這篇教程可以供任何基礎的讀者學習,不管你是否有React方面的經驗。

讓我們開始吧!

Hello World

根據歷史悠久的“傳統”,我們也來寫一個“Hello World”:

import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';

class HelloWorldApp extends Component {
  render() {
    return (
      <Text>Hello world!</Text>
    );
  }
}

// 注意,這里用引號括起來的'HelloWorldApp'必須和你init創建的項目名一致
AppRegistry.registerComponent('HelloWorld_ReactNative', () => HelloWorldApp);

你可以新建一個項目,然后用上面的代碼覆蓋你的index.ios.js或是index.android.js 文件,然后運行看看。

那這段代碼是什么意思呢?

初看這段代碼,可能覺得并不像JavaScript——沒錯,這是“未來”的JavaScript.

首先你需要了解ES2015 (也叫作ES6)——這是一套對JavaScript的語法改進的官方標準。但是這套標準目前還沒有在所有的瀏覽器上完整實現,所以目前而言web開發中還很少使用。React Native內置了對ES2015標準的支持,你可以放心使用而無需擔心兼容性問題。上面的示例代碼中的importfromclassextends、以及() =>箭頭函數等新語法都是ES2015中的特性。如果你不熟悉ES2015的話,可以看看阮一峰老師的書,還有論壇的這篇總結

示例中的這一行<Text>Hello world!</Text>恐怕很多人看起來也覺得陌生。這叫做JSX——是一種在JavaScript中嵌入XML結構的語法。很多傳統的應用框架會設計自有的模板語法,讓你在結構標記中嵌入代碼。React反其道而行之,設計的JSX語法卻是讓你在代碼中嵌入結構標記。初看起來,這種寫法很像web上的HTML,只不過使用的并不是web上常見的標簽如<div>或是<span>等,這里我們使用的是React Native的組件。上面的示例代碼中,使用的是內置的<Text>組件,它專門用來顯示文本。

組件與AppRegistry

上面的代碼定義了一個名為HelloWorldApp的新的組件(Component),并且使用了名為AppRegistry的內置模塊進行了“注冊”操作。你在編寫React Native應用時,肯定會寫出很多新的組件。而一個App的最終界面,其實也就是各式各樣的組件的組合。組件本身結構可以非常簡單——唯一必須的就是在render方法中返回一些用于渲染結構的JSX語句。

AppRegistry模塊則是用來告知React Native哪一個組件被注冊為整個應用的根容器。你無需在此深究,因為一般在整個應用里AppRegistry.registerComponent這個方法只會調用一次。上面的代碼里已經包含了具體的用法,你只需整個復制到index.ios.js或是index.android.js文件中即可運行。

這個示例弱爆了!

……是的。如果想做些更有意思的東西,請接著學習Props屬性。或者可以看看一個稍微復雜些的“電影列表”例子

<二>Props(屬性)

大多數組件在創建時就可以使用各種參數來進行定制。用于定制的這些參數就稱為props(屬性)。

以常見的基礎組件Image為例,在創建一個圖片時,可以傳入一個名為source的prop來指定要顯示的圖片的地址,以及使用名為style的prop來控制其尺寸。

import React, { Component } from 'react';
import { AppRegistry, Image } from 'react-native';

class Bananas extends Component {
  render() {
    let pic = {
      uri: 'http://pic.duowan.com/xunxian/1102/161618998125/161620264846.jpg'
    };
    return (
      <Image source={pic} style={{width: 193, height: 110}} />
    );
  }
}

AppRegistry.registerComponent('HelloWorld_ReactNative', () => Bananas);

譯注:在iOS上使用http鏈接的圖片地址可能不會顯示,參見這篇說明修改

請注意{pic}外圍有一層括號,我們需要用括號來把pic這個變量嵌入到JSX語句中。括號的意思是括號內部為一個js變量或表達式,需要執行后取值。因此我們可以把任意合法的JavaScript表達式通過括號嵌入到JSX語句中。

自定義的組件也可以使用props。通過在不同的場景使用不同的屬性定制,可以盡量提高自定義組件的復用范疇。只需在render函數中引用this.props,然后按需處理即可。下面是一個例子:

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class Greeting extends Component {
  render() {
    return (
      <Text>Hello {this.props.name}!</Text>
    );
  }
}

class LotsOfGreetings extends Component {
  render() {
    return (
      <View style={{alignItems: 'center'}}>
        <Greeting name='Rexxar' />
        <Greeting name='Jaina' />
        <Greeting name='Valeera' />
      </View>
    );
  }
}

AppRegistry.registerComponent('HelloWorld_ReactNative', () => LotsOfGreetings);

我們在Greeting組件中將name作為一個屬性來定制,這樣可以復用這一組件來制作各種不同的“問候語”。上面的例子把Greeting組件寫在JSX語句中,用法和內置組件并無二致——這正是React體系的魅力所在——如果你想搭建一套自己的基礎UI框架,那就放手做吧!

上面的例子出現了一樣新的名為View的組件。View 常用作其他組件的容器,來幫助控制布局和樣式。

僅僅使用props和基礎的TextImage以及View組件,你就已經足以編寫各式各樣的UI組件了。要學習如何動態修改你的界面,那就需要進一步學習State(狀態)的概念

<三>State(狀態)

我們使用兩種數據來控制一個組件:propsstateprops是在父組件中指定,而且一經指定,在被指定的組件的生命周期中則不再改變。 對于需要改變的數據,我們需要使用state

一般來說,你需要在constructor中初始化state(譯注:這是ES6的寫法,早期的很多ES5的例子使用的是getInitialState方法來初始化state,這一做法會逐漸被淘汰),然后在需要修改時調用setState方法。

假如我們需要制作一段不停閃爍的文字。文字內容本身在組件創建時就已經指定好了,所以文字內容應該是一個prop。而文字的顯示或隱藏的狀態(快速的顯隱切換就產生了閃爍的效果)則是隨著時間變化的,因此這一狀態應該寫到state中。

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class Blink extends Component {
  constructor(props) {
    super(props);
    this.state = { showText: true };

    // 每1000毫秒對showText狀態做一次取反操作
    setInterval(() => {
      this.setState(previousState => {
        return { showText: !previousState.showText };
      });
    }, 1000);
  }

  render() {
    // 根據當前showText的值決定是否顯示text內容
    let display = this.state.showText ? this.props.text : ' ';
    return (
      <Text>{display}</Text>
    );
  }
}

class BlinkApp extends Component {
  render() {
    return (
      <View>
        <Blink text='I love to blink' />
        <Blink text='Yes blinking is so great' />
        <Blink text='Why did they ever take this out of HTML' />
        <Blink text='Look at me look at me look at me' />
      </View>
    );
  }
}

AppRegistry.registerComponent('HelloWorld_ReactNative', () => BlinkApp);

實際開發中,我們一般不會在定時器函數(setInterval、setTimeout等)中來操作state。典型的場景是在接收到服務器返回的新數據,或者在用戶輸入數據之后。你也可以使用一些“狀態容器”比如Redux來統一管理數據流(譯注:但我們不建議新手過早去學習redux)。

State的工作原理和React.js完全一致,所以對于處理state的一些更深入的細節,你可以參閱React.Component API

看到這里,你可能覺得我們的例子總是千篇一律的黑色文本,太特么無聊了。那么我們一起來學習一下樣式吧。

<四> 樣式

在React Native中,你并不需要學習什么特殊的語法來定義樣式。我們仍然是使用JavaScript來寫樣式。所有的核心組件都接受名為style的屬性。這些樣式名基本上是遵循了web上的CSS的命名,只是按照JS的語法要求使用了駝峰命名法,例如將background-color改為backgroundColor

style屬性可以是一個普通的JavaScript對象。這是最簡單的用法,因而在示例代碼中很常見。你還可以傳入一個數組——在數組中位置居后的樣式對象比居前的優先級更高,這樣你可以間接實現樣式的繼承。

實際開發中組件的樣式會越來越復雜,我們建議使用StyleSheet.create來集中定義組件的樣式。比如像下面這樣:

import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View } from 'react-native';

class LotsOfStyles extends Component {
  render() {
    return (
      <View>
        <Text style={styles.red}>just red</Text>
        <Text style={styles.bigblue}>just bigblue</Text>
        <Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>
        <Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  bigblue: {
    color: 'blue',
    fontWeight: 'bold',
    fontSize: 30,
  },
  red: {
    color: 'red',
  },
});

AppRegistry.registerComponent('HelloWorld_ReactNative', () => LotsOfStyles);

常見的做法是按順序聲明和使用style屬性,以借鑒CSS中的“層疊”做法(即后聲明的屬性會覆蓋先聲明的同名屬性)。

文本的樣式定義請參閱Text組件的文檔

現在你已經了解如何調整文本樣式了,下面我們要學習的是如何控制組件的尺寸

<五> 高度和寬度

組件的高度和寬度決定了其在屏幕上顯示的尺寸。

指定寬高

最簡單的給組件設定尺寸的方式就是在樣式中指定固定的widthheight。React Native中的尺寸都是無單位的,表示的是與設備像素密度無關的邏輯像素點。

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class FixedDimensionsBasics extends Component {
 render() {
   return (
     <View>
       <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
       <View style={{width: 100, height: 100, backgroundColor: 'skyblue'}} />
       <View style={{width: 150, height: 150, backgroundColor: 'steelblue'}} />
     </View>
   );
 }
};
// 注冊應用(registerComponent)后才能正確渲染
// 注意:只把應用作為一個整體注冊一次,而不是每個組件/模塊都注冊
AppRegistry.registerComponent('HelloWorld_ReactNative', () => FixedDimensionsBasics);

這樣給組件設置尺寸也是一種常見的模式,比如要求在不同尺寸的屏幕上都顯示成一樣的大小。

彈性(Flex)寬高

在組件樣式中使用flex可以使其在可利用的空間中動態地擴張或收縮。一般而言我們會使用flex:1來指定某個組件擴張以撐滿所有剩余的空間。如果有多個并列的子組件使用了flex:1,則這些子組件會平分父容器中剩余的空間。如果這些并列的子組件的flex值不一樣,則誰的值更大,誰占據剩余空間的比例就更大(即占據剩余空間的比等于并列組件間flex值的比)。

組件能夠撐滿剩余空間的前提是其父容器的尺寸不為零。如果父容器既沒有固定的widthheight,也沒有設定flex,則父容器的尺寸為零。其子組件如果使用了flex,也是無法顯示的。


       import React, { Component } from 'react';
       import { AppRegistry, View } from 'react-native';

       class FlexDimensionsBasics extends Component {
         render() {
           return (
             // 試試去掉父View中的`flex: 1`。
             // 則父View不再具有尺寸,因此子組件也無法再撐開。
             // 然后再用`height: 300`來代替父View的`flex: 1`試試看?
             <View style={{flex: 1}}>
               <View style={{flex: 1, backgroundColor: 'powderblue'}} />
               <View style={{flex: 2, backgroundColor: 'skyblue'}} />
               <View style={{flex: 3, backgroundColor: 'steelblue'}} />
             </View>
           );
         }
       };

       AppRegistry.registerComponent('HelloWorld_ReactNative', () => FlexDimensionsBasics);

當你熟練掌握了如何控制組件的尺寸后,下一步可以學習如何在屏幕上排列組件了

<六> 使用Flexbox布局

我們在React Native中使用flexbox規則來指定某個組件的子元素的布局。Flexbox可以在不同屏幕尺寸上提供一致的布局結構。

一般來說,使用flexDirectionalignItemsjustifyContent三個樣式屬性就已經能滿足大多數布局需求。譯注:這里有一份簡易布局圖解,可以給你一個大概的印象。

React Native中的Flexbox的工作原理和web上的CSS基本一致,當然也存在少許差異。首先是默認值不同:flexDirection的默認值是column而不是row,而flex也只能指定一個數字值。

Flex Direction

在組件的style中指定flexDirection可以決定布局的主軸。子元素是應該沿著水平軸(row)方向排列,還是沿著豎直軸(column)方向排列呢?默認值是豎直軸(column)方向。

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class FlexDirectionBasics extends Component {
  render() {
    return (
      // 嘗試把`flexDirection`改為`column`看看
      <View style={{flex: 1, flexDirection: 'row'}}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('HelloWorld_ReactNative', () => FlexDirectionBasics);

Justify Content

在組件的style中指定justifyContent可以決定其子元素沿著主軸排列方式。子元素是應該靠近主軸的起始端還是末尾段分布呢?亦或應該均勻分布?對應的這些可選項有:flex-startcenterflex-endspace-around以及space-between

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class JustifyContentBasics extends Component {
  render() {
    return (
      // 嘗試把`justifyContent`改為`center`看看
      // 嘗試把`flexDirection`改為`row`看看
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'space-between',
      }}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('HelloWorld_ReactNative', () => JustifyContentBasics);

Align Items

在組件的style中指定alignItems可以決定其子元素沿著次軸(與主軸垂直的軸,比如若主軸方向為row,則次軸方向為column)的排列方式。子元素是應該靠近次軸的起始端還是末尾段分布呢?亦或應該均勻分布?對應的這些可選項有:flex-startcenterflex-end以及stretch

注意:要使stretch選項生效的話,子元素在次軸方向上不能有固定的尺寸。以下面的代碼為例:只有將子元素樣式中的width: 50去掉之后,alignItems: 'stretch'才能生效。

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class AlignItemsBasics extends Component {
  render() {
    return (
      // 嘗試把`alignItems`改為`flex-start`看看
      // 嘗試把`justifyContent`改為`flex-end`看看
      // 嘗試把`flexDirection`改為`row`看看
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('HelloWorld_ReactNative', () => AlignItemsBasics);

深入學習

以上我們已經介紹了一些基礎知識,但要運用好布局,我們還需要很多其他的樣式。對于布局有影響的完整樣式列表記錄在這篇文檔中

現在我們已經差不多可以開始真正的開發工作了。哦,忘了還有個常用的知識點:如何使用TextInput組件來處理用戶輸入

<七> 處理文本輸入

TextInput是一個允許用戶輸入文本的基礎組件。它有一個名為onChangeText的屬性,此屬性接受一個函數,而此函數會在文本變化時被調用。另外還有一個名為onSubmitEditing的屬性,會在文本被提交后(用戶按下軟鍵盤上的提交鍵)調用。

假如我們要實現當用戶輸入時,實時將其以單詞為單位翻譯為另一種文字。我們假設這另一種文字來自某個吃貨星球,只有一個單詞: ??。所以"Hello there Bob"將會被翻譯為"??????"。

import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';

class PizzaTranslator extends Component {
  constructor(props) {
    super(props);
    this.state = {text: ''};
  }

  render() {
    return (
      <View style={{padding: 10}}>
        <TextInput
          style={{height: 40}}
          placeholder="Type here to translate!"
          onChangeText={(text) => this.setState({text})}
        />
        <Text style={{padding: 10, fontSize: 42}}>
          {this.state.text.split(' ').map((word) => word && '??').join(' ')}
        </Text>
      </View>
    );
  }
}
// 注冊應用(registerComponent)后才能正確渲染
// 注意:只把應用作為一個整體注冊一次,而不是每個組件/模塊都注冊
AppRegistry.registerComponent('HelloWorld_ReactNative', () => PizzaTranslator);

在上面的例子里,我們把text保存到state中,因為它會隨著時間變化。

文本輸入方面還有很多其他的東西要處理。比如你可能想要在用戶輸入的時候進行驗證,在React的表單組件中的受限組件一節中有一些詳細的示例(注意react中的onChange對應的是rn中的onChangeText)。此外你還需要看看TextInput的文檔

TextInput可能是天然具有“動態狀態”的最簡單的組件了。下面我們來看看另一類控制布局的組件,先從ScrollView開始學習

<八> 如何使用滾動視圖

ScrollView是一個通用的可滾動的容器,你可以在其中放入多個組件和視圖,而且這些組件并不需要是同類型的。ScrollView不僅可以垂直滾動,還能水平滾動(通過horizontal屬性來設置)。

下面的示例代碼創建了一個垂直滾動的ScrollView,其中還混雜了圖片和文字組件。

注:下面的這個./img/favicon.png并不實際存在,請自己準備圖片素材,并改為相對應的正確路徑,具體請參考圖片文檔

import React, { Component } from 'react';
import{ AppRegistry, ScrollView, Image, Text, View } from 'react-native'

class IScrolledDownAndWhatHappenedNextShockedMe extends Component {
  render() {
      return(
        <ScrollView>
          <Text style={{fontSize:96}}>Scroll me plz</Text>
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Text style={{fontSize:96}}>If you like</Text>
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Text style={{fontSize:96}}>Scrolling down</Text>
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Text style={{fontSize:96}}>What's the best</Text>
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Text style={{fontSize:96}}>Framework around?</Text>
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Image source={require('./img/favicon.png')} />
          <Text style={{fontSize:80}}>React Native</Text>
        </ScrollView>
    );
  }
}

// 注冊應用(registerComponent)后才能正確渲染
// 注意:只把應用作為一個整體注冊一次,而不是每個組件/模塊都注冊
AppRegistry.registerComponent(
  'HelloWorld_ReactNative',
  () => IScrolledDownAndWhatHappenedNextShockedMe);

ScrollView適合用來顯示數量不多的滾動元素。放置在ScollView中的所有組件都會被渲染,哪怕有些組件因為內容太長被擠出了屏幕外。如果你需要顯示較長的滾動列表,那么應該使用功能差不多但性能更好的ListView組件。下面我們來看看如何使用ListView

<九> 如何使用長列表

React Native提供了幾個適用于展示長列表數據的組件,一般而言我們會選用FlatList或是SectionList

FlatList組件用于顯示一個垂直的滾動列表,其中的元素之間結構近似而僅數據不同。

FlatList更適于長列表數據,且元素個數可以增刪。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是優先渲染屏幕上可見的元素。

FlatList組件必須的兩個屬性是datarenderItemdata是列表的數據源,而renderItem則從數據源中逐個解析數據,然后返回一個設定好格式的組件來渲染。

下面的例子創建了一個簡單的FlatList,并預設了一些模擬數據。首先是初始化FlatList所需的data,其中的每一項(行)數據之后都在renderItem中被渲染成了Text組件,最后構成整個FlatList

import React, { Component } from 'react';
import { AppRegistry, FlatList, StyleSheet, Text, View } from 'react-native';

export default class FlatListBasics extends Component {
  render() {
    return (
      <View style={styles.container}>
        <FlatList
          data={[
            {key: 'Devin'},
            {key: 'Jackson'},
            {key: 'James'},
            {key: 'Joel'},
            {key: 'John'},
            {key: 'Jillian'},
            {key: 'Jimmy'},
            {key: 'Julie'},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item.key}</Text>}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 22
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})

// skip this line if using Create React Native App
AppRegistry.registerComponent('HelloWorld_ReactNative', () => FlatListBasics);

If you want to render a set of data broken into logical sections, maybe with section headers, then a SectionList is the way to go.

import React, { Component } from 'react';
import { AppRegistry, SectionList, StyleSheet, Text, View } from 'react-native';

export default class SectionListBasics extends Component {
  render() {
    return (
      <View style={styles.container}>
        <SectionList
          sections={[
            {title: 'D', data: ['Devin']},
            {title: 'J', data: ['Jackson', 'James', 'Jillian', 'Jimmy', 'Joel', 'John', 'Julie']},
          ]}
          renderItem={({item}) => <Text style={styles.item}>{item}</Text>}
          renderSectionHeader={({section}) => <Text style={styles.sectionHeader}>{section.title}</Text>}
        />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
   flex: 1,
   paddingTop: 22
  },
  sectionHeader: {
    paddingTop: 2,
    paddingLeft: 10,
    paddingRight: 10,
    paddingBottom: 2,
    fontSize: 14,
    fontWeight: 'bold',
    backgroundColor: 'rgba(247,247,247,1.0)',
  },
  item: {
    padding: 10,
    fontSize: 18,
    height: 44,
  },
})

// skip this line if using Create React Native App
AppRegistry.registerComponent('HelloWorld_ReactNative', () => SectionListBasics);

列表的一個常用場景就是從服務器端取回列表數據然后顯示,要實現這一過程,你可能還需要學習React Native的網絡相關用法.

<十> 網絡

很多移動應用都需要從遠程地址中獲取數據或資源。你可能需要給某個REST API發起POST請求以提交用戶數據,又或者可能僅僅需要從某個服務器上獲取一些靜態內容——以下就是你會用到的東西。新手可以對照這個簡短的視頻教程加深理解。

使用Fetch

React Native提供了和web標準一致的Fetch API,用于滿足開發者訪問網絡的需求。如果你之前使用過XMLHttpRequest(即俗稱的ajax)或是其他的網絡API,那么Fetch用起來將會相當容易上手。這篇文檔只會列出Fetch的基本用法,并不會講述太多細節,你可以使用你喜歡的搜索引擎去搜索fetch api關鍵字以了解更多信息。

發起網絡請求

要從任意地址獲取內容的話,只需簡單地將網址作為參數傳遞給fetch方法即可(fetch這個詞本身也就是獲取的意思):

fetch('https://mywebsite.com/mydata.json')

Fetch還有可選的第二個參數,可以用來定制HTTP請求一些參數。你可以指定header參數,或是指定使用POST方法,又或是提交數據等等:

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    firstParam: 'yourValue',
    secondParam: 'yourOtherValue',
  })
})

譯注:如果你的服務器無法識別上面POST的數據格式,那么可以嘗試傳統的form格式,示例如下:

fetch('https://mywebsite.com/endpoint/', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: 'key1=value1&key2=value2'
})

可以參考Fetch請求文檔來查看所有可用的參數。

處理服務器的響應數據

上面的例子演示了如何發起請求。很多情況下,你還需要處理服務器回復的數據。

網絡請求天然是一種異步操作(譯注:同樣的還有asyncstorage,請不要再問怎樣把異步變成同步!無論在語法層面怎么折騰,它們的異步本質是無法變更的。異步的意思是你應該趁這個時間去做點別的事情,比如顯示loading,而不是讓界面卡住傻等)。Fetch 方法會返回一個Promise,這種模式可以簡化異步風格的代碼(譯注:同樣的,如果你不了解promise,建議使用搜索引擎補課):

getMoviesFromApiAsync() {
  return fetch('https://facebook.github.io/react-native/movies.json')
    .then((response) => response.json())
    .then((responseJson) => {
      return responseJson.movies;
    })
    .catch((error) => {
      console.error(error);
    });
}

你也可以在React Native應用中使用ES7標準中的async/await 語法:

// 注意這個方法前面有async關鍵字
async getMoviesFromApi() {
  try {
    // 注意這里的await語句,其所在的函數必須有async關鍵字聲明
    let response = await fetch('https://facebook.github.io/react-native/movies.json');
    let responseJson = await response.json();
    return responseJson.movies;
  } catch(error) {
    console.error(error);
  }
}

別忘了catch住fetch可能拋出的異常,否則出錯時你可能看不到任何提示。

默認情況下,iOS會阻止所有非https的請求。如果你請求的接口是http協議,那么首先需要添加一個App Transport Security的例外,詳細可參考這篇帖子

使用其他的網絡庫

React Native中已經內置了XMLHttpRequest API(也就是俗稱的ajax)。一些基于XMLHttpRequest封裝的第三方庫也可以使用,例如frisbee或是axios等。但注意不能使用jQuery,因為jQuery中還使用了很多瀏覽器中才有而RN中沒有的東西(所以也不是所有web中的ajax庫都可以直接使用)。

var request = new XMLHttpRequest();
request.onreadystatechange = (e) => {
  if (request.readyState !== 4) {
    return;
  }

  if (request.status === 200) {
    console.log('success', request.responseText);
  } else {
    console.warn('error');
  }
};

request.open('GET', 'https://mywebsite.com/endpoint/');
request.send();

需要注意的是,安全機制與網頁環境有所不同:在應用中你可以訪問任何網站,沒有跨域的限制。

WebSocket支持

React Native還支持WebSocket,這種協議可以在單個TCP連接上提供全雙工的通信信道。

var ws = new WebSocket('ws://host.com/path');

ws.onopen = () => {
  // 打開一個連接

  ws.send('something'); // 發送一個消息
};

ws.onmessage = (e) => {
  // 接收到了一個消息
  console.log(e.data);
};

ws.onerror = (e) => {
  // 發生了一個錯誤
  console.log(e.message);
};

ws.onclose = (e) => {
  // 連接被關閉了
  console.log(e.code, e.reason);
};

現在你的應用已經可以從各種渠道獲取數據了,那么接下來面臨的問題多半就是如何在不同的頁面間組織和串聯內容了。要管理頁面的跳轉,你需要學習使用導航器跳轉頁面

<十一> 其他參考資源

如果你耐心的讀完并理解了本網站上的所有文檔,那么你應該已經可以編寫一個像樣的React Native應用了。但是React Native并不全是某一家公司的作品——它匯聚了成千上萬開源社區開發者的智慧結晶。如果你想深入研究React Native,那么建議不要錯過下面這些參考資源。

常用的第三方庫

如果你正在使用React Native,那你應該已經對React有一定的了解了。React是基礎中的基礎所以我其實不太好意思提這個——但是,如果不幸你屬于“但是”,那么請一定先了解下React,它也非常適合編寫現代化的網站。

開發實踐中的一個常見問題就是如何管理應用的“狀態(state)”。這方面目前最流行的庫非Redux莫屬了。不要被Redux中經常出現的類似"reducer"這樣的概念術語給嚇住了——它其實是個很簡單的庫,網上也有很多優秀的視頻教程(英文) 。。

如果你在尋找具有某個特定功能的第三方庫,那么可以看看別人精心整理的資源列表。這里還有個類似的中文資源列表

示例應用

React Native Playground網站上有很多示例的代碼。這個網站有個很酷的特性:它直接對接了真實設備,可以實時在網頁上顯示運行效果。當然,對于國內用戶來說,可能訪問很困難。

另外就是Facebook的F8開發大會有一個對應的app,這個app現在已經開源,其開發者還詳細地撰寫了相關教程。如果你想學習一個更實際更有深度的例子,那你應該看看這個。

開發工具

Nuclide是Facebook內部所使用的React Native開發工具。它最大的特點是自帶調試功能,并且非常好地支持flow語法規則。(譯注:然而我們還是推薦webstorm或是sublime text)。

Ignite是一套整合了Redux以及一些常見UI組件的腳手架。它帶有一個命令行可以生成app、組件或是容器。如果你喜歡它的選擇搭配,那么不妨一試。

CodePush是由微軟提供的熱更新服務。熱更新可以使你繞過AppStore的審核機制,直接修改已經上架的應用。對于國內用戶,我們也推薦由本網站提供的Pushy熱更新服務,相比CodePush來說,提供了全中文的文檔和技術支持,服務器部署在國內速度更快,還提供了全自動的差量更新方式,大幅節約更新流量,歡迎朋友們試用和反饋意見!

Exponent是一套開發環境,還帶有一個已上架的空應用容器。這樣你可以在沒有原生開發平臺(Xcode或是Android Studio)的情況下直接編寫React Native應用(當然這樣你只能寫js部分代碼而沒法寫原生代碼)。

Deco是一個專為React Native設計的集成開發環境。它可以自動創建新項目、搜索開源組件并插入到項目中。你還可以實時地可視化地調整應用的界面。不過目前還只支持mac。

React Native的交流社區

以下這些都是英文的交流區,我也就不翻譯了……

The React Native Community Facebook group has thousands of developers, and it's pretty active. Come there to show off your project, or ask how other people solved similar problems.

Reactiflux is a Discord chat where a lot of React-related discussion happens, including React Native. Discord is just like Slack except it works better for open source projects with a zillion contributors. Check out the #react-native channel.

The React Twitter account covers both React and React Native. Following that account is a pretty good way to find out what's happening in the world of React.

There are a lot of React Native Meetups that happen around the world. Often there is React Native content in React meetups as well.

Sometimes we have React conferences. We posted the videos from React.js Conf 2016, and we'll probably have more conferences in the future, too. Stay tuned.

歡迎朋友們在下方評論區分享中文教程和資源。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容