RN 的環(huán)境搭建和基礎(chǔ)操作參照官方文檔:查看
學(xué)習(xí)做一個(gè) Movie Fetcher
俗話說(shuō)前人栽樹(shù),后人乘涼.我們?cè)跊](méi)有什么 RN 基礎(chǔ)的時(shí)候,跟著前輩已有的項(xiàng)目教程學(xué)著寫代碼顯然是個(gè)不錯(cuò)的選擇.
參考項(xiàng)目地址:查看 參考源碼:查看
我使用RN 版本的是目前的最新版0.39
(一)第一部分:初學(xué)
1. 模擬數(shù)據(jù)(Mocking data)
在我們寫代碼去獲取加載真實(shí)的數(shù)據(jù)之前,我們先來(lái)模擬一下數(shù)據(jù)。一般我們會(huì)聲明一些常量在JS文件的頭部,僅僅在imports語(yǔ)句下面孟辑。當(dāng)然了,你可以添加在其他一些地方蔫敲,只要你喜歡即可饲嗽。下面是index.ios.js以及index.android.js需要添加的代碼:
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015', posters: {thumbnail: 'http://i.imgur.com/UePbdph.jpg'}},
];
2.渲染一條電影數(shù)據(jù)(Redner a movie)
接下來(lái)我們渲染顯示電影的標(biāo)題,年份以及電影的縮略圖奈嘿∶蚕海縮略圖是React Native中的Image組件進(jìn)行顯示,然后我們需要導(dǎo)入Image組件
import React, {
Component,
} from 'react';
import {
AppRegistry,
Image,
StyleSheet,
Text,
View,
} from 'react-native';
我們修改render()方法來(lái)進(jìn)行渲染該條電影數(shù)據(jù):
render() {
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>
);
}
}
然后我們打開(kāi)開(kāi)發(fā)者菜單/點(diǎn)擊Reload JS,你可以看到"Title"和"2015"這兩個(gè)數(shù)據(jù)袄膏,當(dāng)然你會(huì)注意到Image沒(méi)有任何渲染。這是因?yàn)槟愕腎mage組件沒(méi)有指定寬和高闽瓢。這個(gè)你可以通過(guò)定義Style實(shí)現(xiàn)扣讼,接下來(lái)我們來(lái)寫一個(gè)樣式。
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 53,
height: 81,
},
});
然后再添加樣式:
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
渲染之后可能圖片不會(huì)出現(xiàn)
可以用官方樣例測(cè)試一下
<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
style={{width: 400, height: 400}} />
如果官方圖片可以顯示 說(shuō)明可能是墻的問(wèn)題
建議 把圖片存到本地 通過(guò)其他方法調(diào)用
index.ios.js
/**
* Sample React Native App
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015'},
];
export default class MovieFetcher extends Component {
render() {
var movie = MOCKED_MOVIES_DATA[0];
return (
<View style={styles.container}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
<Image source={require('./img/UePbdph.jpg')}
style={styles.thumbnail} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
thumbnail: {
width: 53,
height: 81,
},
});
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);
這是目前的效果
我們來(lái)分析一下代碼:
1.樣式
const styles = StyleSheet.create({})
這個(gè)樣式部分是與原生 js 最接近的 也很好理解
有種 html 里面加了 className 然后在 css 里面加屬性的感覺(jué)
你只需要通過(guò)JavaScript定義應(yīng)用的樣式即可蒸健。
所有核心的組件都有style的屬性似忧。
該樣式的名稱和屬性值幾乎和Web端的CSS樣式差不多,不過(guò)需要修改成駝峰命名法饺著,例如:這邊使用backgroudColor代替background-color幼衰。
下面兩段代碼的作用是一致的:
...
<Image source={require('./img/UePbdph.jpg')}
style={styles.thumbnail} />
...
const styles = StyleSheet.create({
thumbnail: {
width: 53,
height: 81,
},
});
<Image source={require('./img/UePbdph.jpg')}
style={ width: 53, height: 81} />
而前者 建立樣式表的方式 便于管理和復(fù)用
2.import
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
這個(gè)部分主要添加必要的模塊
3.定義
- 常量
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015'},
];
這個(gè)也沒(méi)什么好說(shuō)的
定義了一個(gè)數(shù)組對(duì)象 里面有一個(gè) json json 作為一個(gè)輕量的存儲(chǔ)方式很適合用來(lái)存一些數(shù)據(jù)
- 類
export default class MovieFetcher extends Component {}
這里定義了一個(gè)叫做MovieFetcher的類
export 可以將該類導(dǎo)出 以便于其他 js 文件導(dǎo)入使用
為方便理解,可以把類的定義看做 函數(shù)定義 ,當(dāng)然這兩者不等同
default 一個(gè)文件中只能用一次
下面這個(gè)類也是合法形式
class Project extends Component{}
4.render()
這個(gè)是一個(gè)渲染器
原生 js 運(yùn)行時(shí)由瀏覽器進(jìn)行渲染 RN 里面通過(guò)虛擬 Dom進(jìn)行渲染
render() 里面是什么呢 是核心組件Core Components
我們?cè)賮?lái)看一眼上面的內(nèi)容
export default class MovieFetcher extends Component {}
- 這里就有個(gè) component
核心組件介紹: 查看
像一些 Text 組件. Image 組件 TextInput 組件等等
要注意的是: 每次使用組件都要在文件頭部看一下有沒(méi)有引入相應(yīng)的模塊
如果這里沒(méi)有引入 Image 模塊
我們剛剛的代碼就要出錯(cuò)啦
然后改一改布局
把內(nèi)容顯示變成這樣
1.圖片和文字作為整體垂直居中
2.文字部分占據(jù)橫向空余空間
(二)第二部分:添加初始化的過(guò)程
平時(shí)我們?cè)诳吹絻?nèi)容加載的時(shí)候 會(huì)有進(jìn)度條或者轉(zhuǎn)動(dòng)的圓圈顯示在界面上 接下里就要做這個(gè)效果
按照教程敲完代碼
可以實(shí)現(xiàn) 圖片還是老問(wèn)題
我們先不管圖片 來(lái)看一下代碼
/**
* Sample React Native App
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
class MovieFetcher extends Component {
constructor(props) {
super(props);
this.state = {
movies: null,
};
}
componentDidMount() {
this.fetchData();
}
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
movies: responseData.movies,
});
})
.done();
}
render() {
if (!this.state.movies) {
return this.renderLoadingView();
}
var movie = this.state.movies[0];
return this.renderMovie(movie);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading movies...
</Text>
</View>
);
}
renderMovie(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>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
rightContainer: {
flex: 1,
},
title: {
fontSize: 30,
marginBottom: 8,
textAlign: 'center',
},
year: {
fontSize: 18,
textAlign: 'center',
},
thumbnail: {
width: 53,
height: 81,
},
});
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);
樣式就不說(shuō)了
來(lái)說(shuō)一說(shuō) Props(屬性)與State(狀態(tài))
-
屬性 props
上面的 image 組件
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
這里的 source就是一個(gè)屬性 用來(lái)選擇圖片的來(lái)源
除了自定義常量可以給屬性賦值外
自定義的組件也是可以使用props的
...
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>
);
}
我們來(lái)改變index.ios.js 跑一下加深一下對(duì) prop 的理解
/**
* Sample React Native App
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
class Greeting extends Component {
render() {
return (
<Text>Hello {this.props.name}!</Text>
);
}
}
class MovieFetcher extends Component {
render() {
return (
<View style={{alignItems: 'center',marginTop: 100,}}>
<Greeting name='Rexxar' />
<Greeting name='Jaina' />
<Greeting name='Valeera' />
</View>
);
}
}
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);
看一下效果
猜測(cè) this.props 里面存了所有的屬性值 調(diào)用 name 顯示 name 值
-
state
不知道大家有沒(méi)有了解過(guò)狀態(tài)機(jī)
狀態(tài)機(jī) 就是一個(gè)狀態(tài)執(zhí)行完就等待觸發(fā)然后跳到下一個(gè)狀態(tài)執(zhí)行的一種機(jī)制.
props是在父組件中進(jìn)行設(shè)置,只要設(shè)置完成那么該在組件的聲明周期中就定死了竞膳,不會(huì)發(fā)生改變刊侯。所以針對(duì)數(shù)據(jù)變化修改的情況滨彻,我們需要使用state屬性亭饵。
一般情況下,我們需要在constructor方法中進(jìn)行初始化state八秃,然后在你想要修改更新的時(shí)候調(diào)用setState方法即可喜德。
state 的改變充當(dāng)了觸發(fā)條件
render()充當(dāng)了執(zhí)行過(guò)程
render() 里面的內(nèi)容就是需要執(zhí)行的內(nèi)容
例如:我們現(xiàn)在需要制作一段不斷進(jìn)行閃動(dòng)的文字效果舍悯,文字內(nèi)容當(dāng)組件創(chuàng)建好的時(shí)候就已經(jīng)指定了萌衬。文字內(nèi)容通過(guò)prop展現(xiàn)秕豫。但是通過(guò)時(shí)間控制文字閃動(dòng)的狀態(tài)通過(guò)state實(shí)現(xiàn)。一起來(lái)看一下如下的代碼:
改變 index.ios.js
/**
* Sample React Native App
*/
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native';
class Blink extends Component {
constructor(props) {
super(props);
this.state = {showText: true};
// Toggle the state every second
setInterval(() => {
this.setState({ showText: !this.state.showText });
}, 1000);
}
render() {
let display = this.state.showText ? this.props.text : ' ';
return (
<Text>{display}</Text>
);
}
}
class MovieFetcher extends Component {
render() {
return (
<View style={{flex: 1,alignItems: 'center',justifyContent: 'center',}}>
<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('MovieFetcher', () => MovieFetcher);
我們來(lái)看一下代碼部分
為了使用 <Blink text='I love to blink' />
自定義了一個(gè)Blink類
class Blink extends Component {}
首先初始化 state
- props來(lái)自于父組件或者自身getDefaultProps
這里就傳入了 <Blink text='I love to blink' />里面的 text 屬性 - 接著初始化了一個(gè)state的showText為true的state對(duì)象
constructor(props) {
super(props);
this.state = {showText: true};
...
}
- 接下來(lái)的定時(shí)器的作用是每隔一秒鐘使得 state 對(duì)象里的 showText 布爾值取反 設(shè)置 state 需要用到 setState 方法
...
setInterval(() => {
this.setState({ showText: !this.state.showText });
}, 1000);
...
- 渲染虛擬 Dom: 當(dāng)showText布爾值為 true 的時(shí)候顯示 反之不顯示
render() {
let display = this.state.showText ? this.props.text : ' ';
return (
<Text>{display}</Text>
);
}
看到這里大家對(duì) prop 和 state 都有一定了解了
-
我們?cè)倩剡^(guò)頭去看之前的代碼
(一) 首先定義一個(gè)全局常量 一個(gè) json 對(duì)象
var REQUEST_URL = 'https://raw.githubusercontent.com/facebook/react-native/master/docs/MoviesExample.json';
(二)然后定義MovieFetcher類
class MovieFetcher extends Component {}
(三) 初始化 state: this.state.movie 設(shè)為 null
...
constructor(props) {
super(props);
this.state = {
movies: null,
};
}
...
(四) 當(dāng)調(diào)用componentDidMount的時(shí)候執(zhí)行this.fetchData()方法
componentDidMount:在render渲染之后回铛,React會(huì)根據(jù)Virtual DOM來(lái)生成真實(shí)DOM茵肃,生成完畢后會(huì)調(diào)用該函數(shù)。在瀏覽器端(React)捞附,我們可以通過(guò)this.getDOMNode()來(lái)拿到相應(yīng)的DOM節(jié)點(diǎn)。然而我們?cè)赗N中并用不到紊婉,在RN中主要在該函數(shù)中執(zhí)行網(wǎng)絡(luò)請(qǐng)求槽片,定時(shí)器開(kāi)啟等相關(guān)操作
componentDidMount方法方法只會(huì)在組件完成加載的時(shí)候調(diào)用一次还栓。
...
componentDidMount() {
this.fetchData();
}
...
(五) this.fetchData()
接下來(lái)我們添加一個(gè)fetchData方法來(lái)加載處理數(shù)據(jù)剩盒。我們需要在數(shù)據(jù)加載成功之后調(diào)用this.setState({moves:data}),要知道該setState方法會(huì)觸發(fā)控件重新渲染,同時(shí)也會(huì)注意到this.state.moves不會(huì)一直未null跟匆。該方法最后我們調(diào)用done()方法玛臂,我們需要確保最后調(diào)用done()方法迹冤,這樣有任何異常我們可以攔截到并且處理。
...
fetchData() {
//fetch ->get方法呐籽,只填寫url參數(shù)
fetch(REQUEST_URL)
//上面一行會(huì)返回響應(yīng)對(duì)象狡蝶,即response
.then((response) => response.json())
//response.json()將返回一個(gè)json類型對(duì)象
.then((responseData) => {
this.setState({
movies: responseData.movies,
});
})
//注意我們?cè)赑romise調(diào)用鏈的最后調(diào)用了done() —— 這樣可以拋出異常而不是簡(jiǎn)單忽略贪惹。
.done();
}
...
(六) render()
當(dāng)moves數(shù)據(jù)為空的時(shí)候顯示一個(gè)正在加載的視圖
不為空的時(shí)候加載 json 里面的數(shù)據(jù)
render() {
if (!this.state.movies) {
return this.renderLoadingView();
}
var movie = this.state.movies[0];
return this.renderMovie(movie);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading movies...
</Text>
</View>
);
}
renderMovie(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>
);
}
(七)試著用豆瓣電影的 API 來(lái)改寫上面的代碼
//地址
"https://api.douban.com/v2/movie/subject/1764796"
改動(dòng):
效果:
(三)第三部分:添加 ListView列表組件
先來(lái)看一下 ListView的官方例子
改變 index.ios.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
ListView,
} from 'react-native';
var MovieFetcher = React.createClass({
getInitialState: function() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
return {
dataSource: ds.cloneWithRows(['row 1', 'row 2','row 3','row 4','row 5','row 6','row 7','row 8']),
};
},
render: function() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
style={{marginTop: 20}}
/>
);
}
});
AppRegistry.registerComponent('MovieFetcher', () => MovieFetcher);
效果:
- getInitialState()
它的 getInitialState方法用于定義初始狀態(tài)并淋,也就是一個(gè)對(duì)象县耽,這個(gè)對(duì)象可以通過(guò) this.state屬性讀取。當(dāng)用戶點(diǎn)擊組件澎剥,導(dǎo)致?tīng)顟B(tài)變化哑姚,this.setState方法就修改狀態(tài)值叙量,每次修改以后宛乃,自動(dòng)調(diào)用 this.render方法,再次渲染組件谆奥。
- rowHasChanged
rowHasChanged(prevRowData, nextRowData):指定我們更新row的策略酸些,一般來(lái)說(shuō)都是prevRowData和nextRowData不相等時(shí)更新row
rowHasChanged函數(shù)可以告訴ListView它是否需要重繪一行數(shù)據(jù)沿侈。
- cloneWithRows(dataBlob, rowIdentities)
該方法接收兩個(gè)參數(shù)缀拭,dataBlob是原始數(shù)據(jù)源蛛淋。在沒(méi)有section褐荷,傳入一個(gè)純數(shù)組的時(shí)候使用此方法。rowIdentities為可選類型合溺,為數(shù)據(jù)源的每一項(xiàng)指明一個(gè)id棠赛。默認(rèn)的id為字符串'0','1','2'...dataBlob.count睛约。
- dataSource ListViewDataSource 設(shè)置ListView的數(shù)據(jù)源
dataSource :該屬性辩涝,用于為L(zhǎng)istView指定當(dāng)前的數(shù)據(jù)源
- renderRow function 方法
(rowData,sectionID,rowID,highlightRow)=>renderable 該方法有四個(gè)參數(shù),其中分別為數(shù)據(jù)源中一條數(shù)據(jù),分組的ID宠进,行的ID实幕,以及標(biāo)記是否是高亮選中的狀態(tài)信息末贾。
renderRow :該屬性用來(lái)標(biāo)示ListView中每一行需要顯示的樣子未舟。參數(shù)表示當(dāng)前行需要顯示的數(shù)據(jù)
開(kāi)始添加 ListView
-
import ListView組件
import {
AppRegistry,
Image,
ListView,
StyleSheet,
Text,
View,
} from 'react-native';
然后我們修改render方法员串,使用ListView渲染加載電影數(shù)據(jù)而不是單條記錄
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
//調(diào)用 renderMovie方法
renderRow={this.renderMovie}
style={styles.listView}
/>
);
}
dataSource是一個(gè)Listview的接口用來(lái)確定在數(shù)據(jù)更新過(guò)程中那些行發(fā)生了變化勇哗。
看上面的代碼你也會(huì)注意到通過(guò)this.state來(lái)訪問(wèn)dataSource,那么接下來(lái)就需要在constructor()構(gòu)造方法中創(chuàng)建一個(gè)空的dataSource。
現(xiàn)在通過(guò)dataSource來(lái)進(jìn)行存儲(chǔ)數(shù)據(jù)寸齐,那么我們需要定義一個(gè)狀態(tài):this.state.loaded來(lái)確保獲取加載數(shù)據(jù)不出現(xiàn)重復(fù)請(qǐng)求存儲(chǔ)欲诺。
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
}
最后我們給ListView控件添加一個(gè)Style樣式風(fēng)格
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
萬(wàn)里長(zhǎng)征終于跨出了第一步~ 撒花~