<一>編寫(xiě)Hello World
React Native看起來(lái)很像React,只不過(guò)其基礎(chǔ)組件是原生組件而非web組件。要理解React Native應(yīng)用的基本結(jié)構(gòu),首先需要了解一些基本的React的概念,比如JSX語(yǔ)法、組件、state狀態(tài)以及props屬性。如果你已經(jīng)了解了React,那么還需要掌握一些React Native特有的知識(shí),比如原生組件的使用。這篇教程可以供任何基礎(chǔ)的讀者學(xué)習(xí),不管你是否有React方面的經(jīng)驗(yàn)。
讓我們開(kāi)始吧!
Hello World
根據(jù)歷史悠久的“傳統(tǒng)”,我們也來(lái)寫(xiě)一個(gè)“Hello World”:
import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';
class HelloWorldApp extends Component {
render() {
return (
<Text>Hello world!</Text>
);
}
}
// 注意,這里用引號(hào)括起來(lái)的'HelloWorldApp'必須和你init創(chuàng)建的項(xiàng)目名一致
AppRegistry.registerComponent('HelloWorld_ReactNative', () => HelloWorldApp);
你可以新建一個(gè)項(xiàng)目,然后用上面的代碼覆蓋你的index.ios.js或是index.android.js 文件,然后運(yùn)行看看。
那這段代碼是什么意思呢?
初看這段代碼,可能覺(jué)得并不像JavaScript——沒(méi)錯(cuò),這是“未來(lái)”的JavaScript.
首先你需要了解ES2015 (也叫作ES6)——這是一套對(duì)JavaScript的語(yǔ)法改進(jìn)的官方標(biāo)準(zhǔn)。但是這套標(biāo)準(zhǔn)目前還沒(méi)有在所有的瀏覽器上完整實(shí)現(xiàn),所以目前而言web開(kāi)發(fā)中還很少使用。React Native內(nèi)置了對(duì)ES2015標(biāo)準(zhǔn)的支持,你可以放心使用而無(wú)需擔(dān)心兼容性問(wèn)題。上面的示例代碼中的import、from、class、extends、以及() =>箭頭函數(shù)等新語(yǔ)法都是ES2015中的特性。如果你不熟悉ES2015的話,可以看看阮一峰老師的書(shū),還有論壇的這篇總結(jié)。
示例中的這一行<Text>Hello world!</Text>恐怕很多人看起來(lái)也覺(jué)得陌生。這叫做JSX——是一種在JavaScript中嵌入XML結(jié)構(gòu)的語(yǔ)法。很多傳統(tǒng)的應(yīng)用框架會(huì)設(shè)計(jì)自有的模板語(yǔ)法,讓你在結(jié)構(gòu)標(biāo)記中嵌入代碼。React反其道而行之,設(shè)計(jì)的JSX語(yǔ)法卻是讓你在代碼中嵌入結(jié)構(gòu)標(biāo)記。初看起來(lái),這種寫(xiě)法很像web上的HTML,只不過(guò)使用的并不是web上常見(jiàn)的標(biāo)簽如<div>或是<span>等,這里我們使用的是React Native的組件。上面的示例代碼中,使用的是內(nèi)置的<Text>組件,它專(zhuān)門(mén)用來(lái)顯示文本。
組件與AppRegistry
上面的代碼定義了一個(gè)名為HelloWorldApp的新的組件(Component),并且使用了名為AppRegistry的內(nèi)置模塊進(jìn)行了“注冊(cè)”操作。你在編寫(xiě)React Native應(yīng)用時(shí),肯定會(huì)寫(xiě)出很多新的組件。而一個(gè)App的最終界面,其實(shí)也就是各式各樣的組件的組合。組件本身結(jié)構(gòu)可以非常簡(jiǎn)單——唯一必須的就是在render方法中返回一些用于渲染結(jié)構(gòu)的JSX語(yǔ)句。
AppRegistry模塊則是用來(lái)告知React Native哪一個(gè)組件被注冊(cè)為整個(gè)應(yīng)用的根容器。你無(wú)需在此深究,因?yàn)橐话阍谡麄€(gè)應(yīng)用里AppRegistry.registerComponent這個(gè)方法只會(huì)調(diào)用一次。上面的代碼里已經(jīng)包含了具體的用法,你只需整個(gè)復(fù)制到index.ios.js或是index.android.js文件中即可運(yùn)行。
這個(gè)示例弱爆了!
……是的。如果想做些更有意思的東西,請(qǐng)接著學(xué)習(xí)Props屬性?;蛘呖梢钥纯匆粋€(gè)稍微復(fù)雜些的“電影列表”例子。
<二>Props(屬性)
大多數(shù)組件在創(chuàng)建時(shí)就可以使用各種參數(shù)來(lái)進(jìn)行定制。用于定制的這些參數(shù)就稱為props(屬性)。
以常見(jiàn)的基礎(chǔ)組件Image為例,在創(chuàng)建一個(gè)圖片時(shí),可以傳入一個(gè)名為source的prop來(lái)指定要顯示的圖片的地址,以及使用名為style的prop來(lái)控制其尺寸。
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鏈接的圖片地址可能不會(huì)顯示,參見(jiàn)這篇說(shuō)明修改。
請(qǐng)注意{pic}外圍有一層括號(hào),我們需要用括號(hào)來(lái)把pic這個(gè)變量嵌入到JSX語(yǔ)句中。括號(hào)的意思是括號(hào)內(nèi)部為一個(gè)js變量或表達(dá)式,需要執(zhí)行后取值。因此我們可以把任意合法的JavaScript表達(dá)式通過(guò)括號(hào)嵌入到JSX語(yǔ)句中。
自定義的組件也可以使用props。通過(guò)在不同的場(chǎng)景使用不同的屬性定制,可以盡量提高自定義組件的復(fù)用范疇。只需在render函數(shù)中引用this.props,然后按需處理即可。下面是一個(gè)例子:
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);
我們?cè)?code>Greeting組件中將name作為一個(gè)屬性來(lái)定制,這樣可以復(fù)用這一組件來(lái)制作各種不同的“問(wèn)候語(yǔ)”。上面的例子把Greeting組件寫(xiě)在JSX語(yǔ)句中,用法和內(nèi)置組件并無(wú)二致——這正是React體系的魅力所在——如果你想搭建一套自己的基礎(chǔ)UI框架,那就放手做吧!
上面的例子出現(xiàn)了一樣新的名為View的組件。View 常用作其他組件的容器,來(lái)幫助控制布局和樣式。
僅僅使用props和基礎(chǔ)的Text、Image以及View組件,你就已經(jīng)足以編寫(xiě)各式各樣的UI組件了。要學(xué)習(xí)如何動(dòng)態(tài)修改你的界面,那就需要進(jìn)一步學(xué)習(xí)State(狀態(tài))的概念。
<三>State(狀態(tài))
我們使用兩種數(shù)據(jù)來(lái)控制一個(gè)組件:props和state。props是在父組件中指定,而且一經(jīng)指定,在被指定的組件的生命周期中則不再改變。 對(duì)于需要改變的數(shù)據(jù),我們需要使用state。
一般來(lái)說(shuō),你需要在constructor中初始化state(譯注:這是ES6的寫(xiě)法,早期的很多ES5的例子使用的是getInitialState方法來(lái)初始化state,這一做法會(huì)逐漸被淘汰),然后在需要修改時(shí)調(diào)用setState方法。
假如我們需要制作一段不停閃爍的文字。文字內(nèi)容本身在組件創(chuàng)建時(shí)就已經(jīng)指定好了,所以文字內(nèi)容應(yīng)該是一個(gè)prop。而文字的顯示或隱藏的狀態(tài)(快速的顯隱切換就產(chǎn)生了閃爍的效果)則是隨著時(shí)間變化的,因此這一狀態(tài)應(yīng)該寫(xiě)到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毫秒對(duì)showText狀態(tài)做一次取反操作
setInterval(() => {
this.setState(previousState => {
return { showText: !previousState.showText };
});
}, 1000);
}
render() {
// 根據(jù)當(dāng)前showText的值決定是否顯示text內(nèi)容
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);
實(shí)際開(kāi)發(fā)中,我們一般不會(huì)在定時(shí)器函數(shù)(setInterval、setTimeout等)中來(lái)操作state。典型的場(chǎng)景是在接收到服務(wù)器返回的新數(shù)據(jù),或者在用戶輸入數(shù)據(jù)之后。你也可以使用一些“狀態(tài)容器”比如Redux來(lái)統(tǒng)一管理數(shù)據(jù)流(譯注:但我們不建議新手過(guò)早去學(xué)習(xí)redux)。
State的工作原理和React.js完全一致,所以對(duì)于處理state的一些更深入的細(xì)節(jié),你可以參閱React.Component API。
看到這里,你可能覺(jué)得我們的例子總是千篇一律的黑色文本,太特么無(wú)聊了。那么我們一起來(lái)學(xué)習(xí)一下樣式吧。
<四> 樣式
在React Native中,你并不需要學(xué)習(xí)什么特殊的語(yǔ)法來(lái)定義樣式。我們?nèi)匀皇鞘褂肑avaScript來(lái)寫(xiě)樣式。所有的核心組件都接受名為style的屬性。這些樣式名基本上是遵循了web上的CSS的命名,只是按照J(rèn)S的語(yǔ)法要求使用了駝峰命名法,例如將background-color改為backgroundColor。
style屬性可以是一個(gè)普通的JavaScript對(duì)象。這是最簡(jiǎn)單的用法,因而在示例代碼中很常見(jiàn)。你還可以傳入一個(gè)數(shù)組——在數(shù)組中位置居后的樣式對(duì)象比居前的優(yōu)先級(jí)更高,這樣你可以間接實(shí)現(xiàn)樣式的繼承。
實(shí)際開(kāi)發(fā)中組件的樣式會(huì)越來(lái)越復(fù)雜,我們建議使用StyleSheet.create來(lái)集中定義組件的樣式。比如像下面這樣:
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);
常見(jiàn)的做法是按順序聲明和使用style屬性,以借鑒CSS中的“層疊”做法(即后聲明的屬性會(huì)覆蓋先聲明的同名屬性)。
文本的樣式定義請(qǐng)參閱Text組件的文檔。
現(xiàn)在你已經(jīng)了解如何調(diào)整文本樣式了,下面我們要學(xué)習(xí)的是如何控制組件的尺寸。
<五> 高度和寬度
組件的高度和寬度決定了其在屏幕上顯示的尺寸。
指定寬高
最簡(jiǎn)單的給組件設(shè)定尺寸的方式就是在樣式中指定固定的width和height。React Native中的尺寸都是無(wú)單位的,表示的是與設(shè)備像素密度無(wú)關(guān)的邏輯像素點(diǎn)。
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>
);
}
};
// 注冊(cè)應(yīng)用(registerComponent)后才能正確渲染
// 注意:只把應(yīng)用作為一個(gè)整體注冊(cè)一次,而不是每個(gè)組件/模塊都注冊(cè)
AppRegistry.registerComponent('HelloWorld_ReactNative', () => FixedDimensionsBasics);
這樣給組件設(shè)置尺寸也是一種常見(jiàn)的模式,比如要求在不同尺寸的屏幕上都顯示成一樣的大小。
彈性(Flex)寬高
在組件樣式中使用flex可以使其在可利用的空間中動(dòng)態(tài)地?cái)U(kuò)張或收縮。一般而言我們會(huì)使用flex:1來(lái)指定某個(gè)組件擴(kuò)張以撐滿所有剩余的空間。如果有多個(gè)并列的子組件使用了flex:1,則這些子組件會(huì)平分父容器中剩余的空間。如果這些并列的子組件的flex值不一樣,則誰(shuí)的值更大,誰(shuí)占據(jù)剩余空間的比例就更大(即占據(jù)剩余空間的比等于并列組件間flex值的比)。
組件能夠撐滿剩余空間的前提是其父容器的尺寸不為零。如果父容器既沒(méi)有固定的
width和height,也沒(méi)有設(shè)定flex,則父容器的尺寸為零。其子組件如果使用了flex,也是無(wú)法顯示的。
import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';
class FlexDimensionsBasics extends Component {
render() {
return (
// 試試去掉父View中的`flex: 1`。
// 則父View不再具有尺寸,因此子組件也無(wú)法再撐開(kāi)。
// 然后再用`height: 300`來(lái)代替父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);
當(dāng)你熟練掌握了如何控制組件的尺寸后,下一步可以學(xué)習(xí)如何在屏幕上排列組件了。
<六> 使用Flexbox布局
我們?cè)赗eact Native中使用flexbox規(guī)則來(lái)指定某個(gè)組件的子元素的布局。Flexbox可以在不同屏幕尺寸上提供一致的布局結(jié)構(gòu)。
一般來(lái)說(shuō),使用flexDirection、alignItems和 justifyContent三個(gè)樣式屬性就已經(jīng)能滿足大多數(shù)布局需求。譯注:這里有一份簡(jiǎn)易布局圖解,可以給你一個(gè)大概的印象。
React Native中的Flexbox的工作原理和web上的CSS基本一致,當(dāng)然也存在少許差異。首先是默認(rèn)值不同:
flexDirection的默認(rèn)值是column而不是row,而flex也只能指定一個(gè)數(shù)字值。
Flex Direction
在組件的style中指定flexDirection可以決定布局的主軸。子元素是應(yīng)該沿著水平軸(row)方向排列,還是沿著豎直軸(column)方向排列呢?默認(rèn)值是豎直軸(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可以決定其子元素沿著主軸的排列方式。子元素是應(yīng)該靠近主軸的起始端還是末尾段分布呢?亦或應(yīng)該均勻分布?對(duì)應(yīng)的這些可選項(xiàng)有:flex-start、center、flex-end、space-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可以決定其子元素沿著次軸(與主軸垂直的軸,比如若主軸方向?yàn)?code>row,則次軸方向?yàn)?code>column)的排列方式。子元素是應(yīng)該靠近次軸的起始端還是末尾段分布呢?亦或應(yīng)該均勻分布?對(duì)應(yīng)的這些可選項(xiàng)有:flex-start、center、flex-end以及stretch。
注意:要使
stretch選項(xiàng)生效的話,子元素在次軸方向上不能有固定的尺寸。以下面的代碼為例:只有將子元素樣式中的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);
深入學(xué)習(xí)
以上我們已經(jīng)介紹了一些基礎(chǔ)知識(shí),但要運(yùn)用好布局,我們還需要很多其他的樣式。對(duì)于布局有影響的完整樣式列表記錄在這篇文檔中。
現(xiàn)在我們已經(jīng)差不多可以開(kāi)始真正的開(kāi)發(fā)工作了。哦,忘了還有個(gè)常用的知識(shí)點(diǎn):如何使用TextInput組件來(lái)處理用戶輸入。
<七> 處理文本輸入
TextInput是一個(gè)允許用戶輸入文本的基礎(chǔ)組件。它有一個(gè)名為onChangeText的屬性,此屬性接受一個(gè)函數(shù),而此函數(shù)會(huì)在文本變化時(shí)被調(diào)用。另外還有一個(gè)名為onSubmitEditing的屬性,會(huì)在文本被提交后(用戶按下軟鍵盤(pán)上的提交鍵)調(diào)用。
假如我們要實(shí)現(xiàn)當(dāng)用戶輸入時(shí),實(shí)時(shí)將其以單詞為單位翻譯為另一種文字。我們假設(shè)這另一種文字來(lái)自某個(gè)吃貨星球,只有一個(gè)單詞: ??。所以"Hello there Bob"將會(huì)被翻譯為"??????"。
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>
);
}
}
// 注冊(cè)應(yīng)用(registerComponent)后才能正確渲染
// 注意:只把應(yīng)用作為一個(gè)整體注冊(cè)一次,而不是每個(gè)組件/模塊都注冊(cè)
AppRegistry.registerComponent('HelloWorld_ReactNative', () => PizzaTranslator);
在上面的例子里,我們把text保存到state中,因?yàn)樗鼤?huì)隨著時(shí)間變化。
文本輸入方面還有很多其他的東西要處理。比如你可能想要在用戶輸入的時(shí)候進(jìn)行驗(yàn)證,在React的表單組件中的受限組件一節(jié)中有一些詳細(xì)的示例(注意react中的onChange對(duì)應(yīng)的是rn中的onChangeText)。此外你還需要看看TextInput的文檔。
TextInput可能是天然具有“動(dòng)態(tài)狀態(tài)”的最簡(jiǎn)單的組件了。下面我們來(lái)看看另一類(lèi)控制布局的組件,先從ScrollView開(kāi)始學(xué)習(xí)。
<八> 如何使用滾動(dòng)視圖
ScrollView是一個(gè)通用的可滾動(dòng)的容器,你可以在其中放入多個(gè)組件和視圖,而且這些組件并不需要是同類(lèi)型的。ScrollView不僅可以垂直滾動(dòng),還能水平滾動(dòng)(通過(guò)horizontal屬性來(lái)設(shè)置)。
下面的示例代碼創(chuàng)建了一個(gè)垂直滾動(dòng)的ScrollView,其中還混雜了圖片和文字組件。
注:下面的這個(gè)./img/favicon.png并不實(shí)際存在,請(qǐng)自己準(zhǔn)備圖片素材,并改為相對(duì)應(yīng)的正確路徑,具體請(qǐng)參考圖片文檔。
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>
);
}
}
// 注冊(cè)應(yīng)用(registerComponent)后才能正確渲染
// 注意:只把應(yīng)用作為一個(gè)整體注冊(cè)一次,而不是每個(gè)組件/模塊都注冊(cè)
AppRegistry.registerComponent(
'HelloWorld_ReactNative',
() => IScrolledDownAndWhatHappenedNextShockedMe);
ScrollView適合用來(lái)顯示數(shù)量不多的滾動(dòng)元素。放置在ScollView中的所有組件都會(huì)被渲染,哪怕有些組件因?yàn)閮?nèi)容太長(zhǎng)被擠出了屏幕外。如果你需要顯示較長(zhǎng)的滾動(dòng)列表,那么應(yīng)該使用功能差不多但性能更好的ListView組件。下面我們來(lái)看看如何使用ListView。
<九> 如何使用長(zhǎng)列表
React Native提供了幾個(gè)適用于展示長(zhǎng)列表數(shù)據(jù)的組件,一般而言我們會(huì)選用FlatList或是SectionList。
FlatList組件用于顯示一個(gè)垂直的滾動(dòng)列表,其中的元素之間結(jié)構(gòu)近似而僅數(shù)據(jù)不同。
FlatList更適于長(zhǎng)列表數(shù)據(jù),且元素個(gè)數(shù)可以增刪。和ScrollView不同的是,FlatList并不立即渲染所有元素,而是優(yōu)先渲染屏幕上可見(jiàn)的元素。
FlatList組件必須的兩個(gè)屬性是data和renderItem。data是列表的數(shù)據(jù)源,而renderItem則從數(shù)據(jù)源中逐個(gè)解析數(shù)據(jù),然后返回一個(gè)設(shè)定好格式的組件來(lái)渲染。
下面的例子創(chuàng)建了一個(gè)簡(jiǎn)單的FlatList,并預(yù)設(shè)了一些模擬數(shù)據(jù)。首先是初始化FlatList所需的data,其中的每一項(xiàng)(行)數(shù)據(jù)之后都在renderItem中被渲染成了Text組件,最后構(gòu)成整個(gè)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);
列表的一個(gè)常用場(chǎng)景就是從服務(wù)器端取回列表數(shù)據(jù)然后顯示,要實(shí)現(xiàn)這一過(guò)程,你可能還需要學(xué)習(xí)React Native的網(wǎng)絡(luò)相關(guān)用法.
<十> 網(wǎng)絡(luò)
很多移動(dòng)應(yīng)用都需要從遠(yuǎn)程地址中獲取數(shù)據(jù)或資源。你可能需要給某個(gè)REST API發(fā)起POST請(qǐng)求以提交用戶數(shù)據(jù),又或者可能僅僅需要從某個(gè)服務(wù)器上獲取一些靜態(tài)內(nèi)容——以下就是你會(huì)用到的東西。新手可以對(duì)照這個(gè)簡(jiǎn)短的視頻教程加深理解。
使用Fetch
React Native提供了和web標(biāo)準(zhǔn)一致的Fetch API,用于滿足開(kāi)發(fā)者訪問(wèn)網(wǎng)絡(luò)的需求。如果你之前使用過(guò)XMLHttpRequest(即俗稱的ajax)或是其他的網(wǎng)絡(luò)API,那么Fetch用起來(lái)將會(huì)相當(dāng)容易上手。這篇文檔只會(huì)列出Fetch的基本用法,并不會(huì)講述太多細(xì)節(jié),你可以使用你喜歡的搜索引擎去搜索fetch api關(guān)鍵字以了解更多信息。
發(fā)起網(wǎng)絡(luò)請(qǐng)求
要從任意地址獲取內(nèi)容的話,只需簡(jiǎn)單地將網(wǎng)址作為參數(shù)傳遞給fetch方法即可(fetch這個(gè)詞本身也就是獲取的意思):
fetch('https://mywebsite.com/mydata.json')
Fetch還有可選的第二個(gè)參數(shù),可以用來(lái)定制HTTP請(qǐng)求一些參數(shù)。你可以指定header參數(shù),或是指定使用POST方法,又或是提交數(shù)據(jù)等等:
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
firstParam: 'yourValue',
secondParam: 'yourOtherValue',
})
})
譯注:如果你的服務(wù)器無(wú)法識(shí)別上面POST的數(shù)據(jù)格式,那么可以嘗試傳統(tǒng)的form格式,示例如下:
fetch('https://mywebsite.com/endpoint/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'key1=value1&key2=value2'
})
可以參考Fetch請(qǐng)求文檔來(lái)查看所有可用的參數(shù)。
處理服務(wù)器的響應(yīng)數(shù)據(jù)
上面的例子演示了如何發(fā)起請(qǐng)求。很多情況下,你還需要處理服務(wù)器回復(fù)的數(shù)據(jù)。
網(wǎng)絡(luò)請(qǐng)求天然是一種異步操作(譯注:同樣的還有asyncstorage,請(qǐng)不要再問(wèn)怎樣把異步變成同步!無(wú)論在語(yǔ)法層面怎么折騰,它們的異步本質(zhì)是無(wú)法變更的。異步的意思是你應(yīng)該趁這個(gè)時(shí)間去做點(diǎn)別的事情,比如顯示loading,而不是讓界面卡住傻等)。Fetch 方法會(huì)返回一個(gè)Promise,這種模式可以簡(jiǎn)化異步風(fēng)格的代碼(譯注:同樣的,如果你不了解promise,建議使用搜索引擎補(bǔ)課):
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應(yīng)用中使用ES7標(biāo)準(zhǔn)中的async/await 語(yǔ)法:
// 注意這個(gè)方法前面有async關(guān)鍵字
async getMoviesFromApi() {
try {
// 注意這里的await語(yǔ)句,其所在的函數(shù)必須有async關(guān)鍵字聲明
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可能拋出的異常,否則出錯(cuò)時(shí)你可能看不到任何提示。
默認(rèn)情況下,iOS會(huì)阻止所有非https的請(qǐng)求。如果你請(qǐng)求的接口是http協(xié)議,那么首先需要添加一個(gè)App Transport Security的例外,詳細(xì)可參考這篇帖子。
使用其他的網(wǎng)絡(luò)庫(kù)
React Native中已經(jīng)內(nèi)置了XMLHttpRequest API(也就是俗稱的ajax)。一些基于XMLHttpRequest封裝的第三方庫(kù)也可以使用,例如frisbee或是axios等。但注意不能使用jQuery,因?yàn)閖Query中還使用了很多瀏覽器中才有而RN中沒(méi)有的東西(所以也不是所有web中的ajax庫(kù)都可以直接使用)。
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();
需要注意的是,安全機(jī)制與網(wǎng)頁(yè)環(huán)境有所不同:在應(yīng)用中你可以訪問(wèn)任何網(wǎng)站,沒(méi)有跨域的限制。
WebSocket支持
React Native還支持WebSocket,這種協(xié)議可以在單個(gè)TCP連接上提供全雙工的通信信道。
var ws = new WebSocket('ws://host.com/path');
ws.onopen = () => {
// 打開(kāi)一個(gè)連接
ws.send('something'); // 發(fā)送一個(gè)消息
};
ws.onmessage = (e) => {
// 接收到了一個(gè)消息
console.log(e.data);
};
ws.onerror = (e) => {
// 發(fā)生了一個(gè)錯(cuò)誤
console.log(e.message);
};
ws.onclose = (e) => {
// 連接被關(guān)閉了
console.log(e.code, e.reason);
};
現(xiàn)在你的應(yīng)用已經(jīng)可以從各種渠道獲取數(shù)據(jù)了,那么接下來(lái)面臨的問(wèn)題多半就是如何在不同的頁(yè)面間組織和串聯(lián)內(nèi)容了。要管理頁(yè)面的跳轉(zhuǎn),你需要學(xué)習(xí)使用導(dǎo)航器跳轉(zhuǎn)頁(yè)面。
<十一> 其他參考資源
如果你耐心的讀完并理解了本網(wǎng)站上的所有文檔,那么你應(yīng)該已經(jīng)可以編寫(xiě)一個(gè)像樣的React Native應(yīng)用了。但是React Native并不全是某一家公司的作品——它匯聚了成千上萬(wàn)開(kāi)源社區(qū)開(kāi)發(fā)者的智慧結(jié)晶。如果你想深入研究React Native,那么建議不要錯(cuò)過(guò)下面這些參考資源。
常用的第三方庫(kù)
如果你正在使用React Native,那你應(yīng)該已經(jīng)對(duì)React有一定的了解了。React是基礎(chǔ)中的基礎(chǔ)所以我其實(shí)不太好意思提這個(gè)——但是,如果不幸你屬于“但是”,那么請(qǐng)一定先了解下React,它也非常適合編寫(xiě)現(xiàn)代化的網(wǎng)站。
開(kāi)發(fā)實(shí)踐中的一個(gè)常見(jiàn)問(wèn)題就是如何管理應(yīng)用的“狀態(tài)(state)”。這方面目前最流行的庫(kù)非Redux莫屬了。不要被Redux中經(jīng)常出現(xiàn)的類(lèi)似"reducer"這樣的概念術(shù)語(yǔ)給嚇住了——它其實(shí)是個(gè)很簡(jiǎn)單的庫(kù),網(wǎng)上也有很多優(yōu)秀的視頻教程(英文) 。。
如果你在尋找具有某個(gè)特定功能的第三方庫(kù),那么可以看看別人精心整理的資源列表。這里還有個(gè)類(lèi)似的中文資源列表。
示例應(yīng)用
在React Native Playground網(wǎng)站上有很多示例的代碼。這個(gè)網(wǎng)站有個(gè)很酷的特性:它直接對(duì)接了真實(shí)設(shè)備,可以實(shí)時(shí)在網(wǎng)頁(yè)上顯示運(yùn)行效果。當(dāng)然,對(duì)于國(guó)內(nèi)用戶來(lái)說(shuō),可能訪問(wèn)很困難。
另外就是Facebook的F8開(kāi)發(fā)大會(huì)有一個(gè)對(duì)應(yīng)的app,這個(gè)app現(xiàn)在已經(jīng)開(kāi)源,其開(kāi)發(fā)者還詳細(xì)地撰寫(xiě)了相關(guān)教程。如果你想學(xué)習(xí)一個(gè)更實(shí)際更有深度的例子,那你應(yīng)該看看這個(gè)。
開(kāi)發(fā)工具
Nuclide是Facebook內(nèi)部所使用的React Native開(kāi)發(fā)工具。它最大的特點(diǎn)是自帶調(diào)試功能,并且非常好地支持flow語(yǔ)法規(guī)則。(譯注:然而我們還是推薦webstorm或是sublime text)。
Ignite是一套整合了Redux以及一些常見(jiàn)UI組件的腳手架。它帶有一個(gè)命令行可以生成app、組件或是容器。如果你喜歡它的選擇搭配,那么不妨一試。
CodePush是由微軟提供的熱更新服務(wù)。熱更新可以使你繞過(guò)AppStore的審核機(jī)制,直接修改已經(jīng)上架的應(yīng)用。對(duì)于國(guó)內(nèi)用戶,我們也推薦由本網(wǎng)站提供的Pushy熱更新服務(wù),相比CodePush來(lái)說(shuō),提供了全中文的文檔和技術(shù)支持,服務(wù)器部署在國(guó)內(nèi)速度更快,還提供了全自動(dòng)的差量更新方式,大幅節(jié)約更新流量,歡迎朋友們?cè)囉煤头答佉庖?jiàn)!
Exponent是一套開(kāi)發(fā)環(huán)境,還帶有一個(gè)已上架的空應(yīng)用容器。這樣你可以在沒(méi)有原生開(kāi)發(fā)平臺(tái)(Xcode或是Android Studio)的情況下直接編寫(xiě)React Native應(yīng)用(當(dāng)然這樣你只能寫(xiě)js部分代碼而沒(méi)法寫(xiě)原生代碼)。
Deco是一個(gè)專(zhuān)為React Native設(shè)計(jì)的集成開(kāi)發(fā)環(huán)境。它可以自動(dòng)創(chuàng)建新項(xiàng)目、搜索開(kāi)源組件并插入到項(xiàng)目中。你還可以實(shí)時(shí)地可視化地調(diào)整應(yīng)用的界面。不過(guò)目前還只支持mac。
React Native的交流社區(qū)
以下這些都是英文的交流區(qū),我也就不翻譯了……
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.
歡迎朋友們?cè)谙路皆u(píng)論區(qū)分享中文教程和資源。