RN 的環(huán)境搭建和基礎(chǔ)操作參照官方文檔:查看
學(xué)習(xí)做一個 Movie Fetcher
俗話說前人栽樹,后人乘涼.我們在沒有什么 RN 基礎(chǔ)的時候,跟著前輩已有的項目教程學(xué)著寫代碼顯然是個不錯的選擇.
參考項目地址:查看 參考源碼:查看
我使用RN 版本的是目前的最新版0.39
(一)第一部分:初學(xué)
1. 模擬數(shù)據(jù)(Mocking data)
在我們寫代碼去獲取加載真實(shí)的數(shù)據(jù)之前,我們先來模擬一下數(shù)據(jù)。一般我們會聲明一些常量在JS文件的頭部,僅僅在imports語句下面。當(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)
接下來我們渲染顯示電影的標(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()方法來進(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>
);
}
}
然后我們打開開發(fā)者菜單/點(diǎn)擊Reload JS,你可以看到"Title"和"2015"這兩個數(shù)據(jù),當(dāng)然你會注意到Image沒有任何渲染。這是因為你的Image組件沒有指定寬和高。這個你可以通過定義Style實(shí)現(xiàn),接下來我們來寫一個樣式。
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}
/>
渲染之后可能圖片不會出現(xiàn)
可以用官方樣例測試一下
<Image source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
style={{width: 400, height: 400}} />
如果官方圖片可以顯示 說明可能是墻的問題
建議 把圖片存到本地 通過其他方法調(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);
這是目前的效果
我們來分析一下代碼:
1.樣式
const styles = StyleSheet.create({})
這個樣式部分是與原生 js 最接近的 也很好理解
有種 html 里面加了 className 然后在 css 里面加屬性的感覺
你只需要通過JavaScript定義應(yīng)用的樣式即可。
所有核心的組件都有style的屬性。
該樣式的名稱和屬性值幾乎和Web端的CSS樣式差不多,不過需要修改成駝峰命名法,例如:這邊使用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';
這個部分主要添加必要的模塊
3.定義
- 常量
var MOCKED_MOVIES_DATA = [
{title: 'Title', year: '2015'},
];
這個也沒什么好說的
定義了一個數(shù)組對象 里面有一個 json json 作為一個輕量的存儲方式很適合用來存一些數(shù)據(jù)
- 類
export default class MovieFetcher extends Component {}
這里定義了一個叫做MovieFetcher的類
export 可以將該類導(dǎo)出 以便于其他 js 文件導(dǎo)入使用
為方便理解,可以把類的定義看做 函數(shù)定義 ,當(dāng)然這兩者不等同
default 一個文件中只能用一次
下面這個類也是合法形式
class Project extends Component{}
4.render()
這個是一個渲染器
原生 js 運(yùn)行時由瀏覽器進(jìn)行渲染 RN 里面通過虛擬 Dom進(jìn)行渲染
render() 里面是什么呢 是核心組件Core Components
我們再來看一眼上面的內(nèi)容
export default class MovieFetcher extends Component {}
- 這里就有個 component
核心組件介紹: 查看
像一些 Text 組件. Image 組件 TextInput 組件等等
要注意的是: 每次使用組件都要在文件頭部看一下有沒有引入相應(yīng)的模塊
如果這里沒有引入 Image 模塊
我們剛剛的代碼就要出錯啦
然后改一改布局
把內(nèi)容顯示變成這樣
1.圖片和文字作為整體垂直居中
2.文字部分占據(jù)橫向空余空間
(二)第二部分:添加初始化的過程
平時我們在看到內(nèi)容加載的時候 會有進(jìn)度條或者轉(zhuǎn)動的圓圈顯示在界面上 接下里就要做這個效果
按照教程敲完代碼
可以實(shí)現(xiàn) 圖片還是老問題
我們先不管圖片 來看一下代碼
/**
* 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);
樣式就不說了
來說一說 Props(屬性)與State(狀態(tài))
-
屬性 props
上面的 image 組件
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
這里的 source就是一個屬性 用來選擇圖片的來源
除了自定義常量可以給屬性賦值外
自定義的組件也是可以使用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>
);
}
我們來改變index.ios.js 跑一下加深一下對 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);
看一下效果
猜測 this.props 里面存了所有的屬性值 調(diào)用 name 顯示 name 值
-
state
不知道大家有沒有了解過狀態(tài)機(jī)
狀態(tài)機(jī) 就是一個狀態(tài)執(zhí)行完就等待觸發(fā)然后跳到下一個狀態(tài)執(zhí)行的一種機(jī)制.
props是在父組件中進(jìn)行設(shè)置,只要設(shè)置完成那么該在組件的聲明周期中就定死了,不會發(fā)生改變。所以針對數(shù)據(jù)變化修改的情況,我們需要使用state屬性。
一般情況下,我們需要在constructor方法中進(jìn)行初始化state,然后在你想要修改更新的時候調(diào)用setState方法即可。
state 的改變充當(dāng)了觸發(fā)條件
render()充當(dāng)了執(zhí)行過程
render() 里面的內(nèi)容就是需要執(zhí)行的內(nèi)容
例如:我們現(xiàn)在需要制作一段不斷進(jìn)行閃動的文字效果,文字內(nèi)容當(dāng)組件創(chuàng)建好的時候就已經(jīng)指定了。文字內(nèi)容通過prop展現(xiàn)。但是通過時間控制文字閃動的狀態(tài)通過state實(shí)現(xiàn)。一起來看一下如下的代碼:
改變 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);
我們來看一下代碼部分
為了使用 <Blink text='I love to blink' />
自定義了一個Blink類
class Blink extends Component {}
首先初始化 state
- props來自于父組件或者自身getDefaultProps
這里就傳入了 <Blink text='I love to blink' />里面的 text 屬性 - 接著初始化了一個state的showText為true的state對象
constructor(props) {
super(props);
this.state = {showText: true};
...
}
- 接下來的定時器的作用是每隔一秒鐘使得 state 對象里的 showText 布爾值取反 設(shè)置 state 需要用到 setState 方法
...
setInterval(() => {
this.setState({ showText: !this.state.showText });
}, 1000);
...
- 渲染虛擬 Dom: 當(dāng)showText布爾值為 true 的時候顯示 反之不顯示
render() {
let display = this.state.showText ? this.props.text : ' ';
return (
<Text>{display}</Text>
);
}
看到這里大家對 prop 和 state 都有一定了解了
-
我們再回過頭去看之前的代碼
(一) 首先定義一個全局常量 一個 json 對象
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的時候執(zhí)行this.fetchData()方法
componentDidMount:在render渲染之后,React會根據(jù)Virtual DOM來生成真實(shí)DOM,生成完畢后會調(diào)用該函數(shù)。在瀏覽器端(React),我們可以通過this.getDOMNode()來拿到相應(yīng)的DOM節(jié)點(diǎn)。然而我們在RN中并用不到,在RN中主要在該函數(shù)中執(zhí)行網(wǎng)絡(luò)請求,定時器開啟等相關(guān)操作
componentDidMount方法方法只會在組件完成加載的時候調(diào)用一次。
...
componentDidMount() {
this.fetchData();
}
...
(五) this.fetchData()
接下來我們添加一個fetchData方法來加載處理數(shù)據(jù)。我們需要在數(shù)據(jù)加載成功之后調(diào)用this.setState({moves:data}),要知道該setState方法會觸發(fā)控件重新渲染,同時也會注意到this.state.moves不會一直未null。該方法最后我們調(diào)用done()方法,我們需要確保最后調(diào)用done()方法,這樣有任何異常我們可以攔截到并且處理。
...
fetchData() {
//fetch ->get方法,只填寫url參數(shù)
fetch(REQUEST_URL)
//上面一行會返回響應(yīng)對象,即response
.then((response) => response.json())
//response.json()將返回一個json類型對象
.then((responseData) => {
this.setState({
movies: responseData.movies,
});
})
//注意我們在Promise調(diào)用鏈的最后調(diào)用了done() —— 這樣可以拋出異常而不是簡單忽略。
.done();
}
...
(六) render()
當(dāng)moves數(shù)據(jù)為空的時候顯示一個正在加載的視圖
不為空的時候加載 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 來改寫上面的代碼
//地址
"https://api.douban.com/v2/movie/subject/1764796"
改動:
效果:
(三)第三部分:添加 ListView列表組件
先來看一下 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),也就是一個對象,這個對象可以通過 this.state屬性讀取。當(dāng)用戶點(diǎn)擊組件,導(dǎo)致狀態(tài)變化,this.setState方法就修改狀態(tài)值,每次修改以后,自動調(diào)用 this.render方法,再次渲染組件。
- rowHasChanged
rowHasChanged(prevRowData, nextRowData):指定我們更新row的策略,一般來說都是prevRowData和nextRowData不相等時更新row
rowHasChanged函數(shù)可以告訴ListView它是否需要重繪一行數(shù)據(jù)。
- cloneWithRows(dataBlob, rowIdentities)
該方法接收兩個參數(shù),dataBlob是原始數(shù)據(jù)源。在沒有section,傳入一個純數(shù)組的時候使用此方法。rowIdentities為可選類型,為數(shù)據(jù)源的每一項指明一個id。默認(rèn)的id為字符串'0','1','2'...dataBlob.count。
- dataSource ListViewDataSource 設(shè)置ListView的數(shù)據(jù)源
dataSource :該屬性,用于為ListView指定當(dāng)前的數(shù)據(jù)源
- renderRow function 方法
(rowData,sectionID,rowID,highlightRow)=>renderable 該方法有四個參數(shù),其中分別為數(shù)據(jù)源中一條數(shù)據(jù),分組的ID,行的ID,以及標(biāo)記是否是高亮選中的狀態(tài)信息。
renderRow :該屬性用來標(biāo)示ListView中每一行需要顯示的樣子。參數(shù)表示當(dāng)前行需要顯示的數(shù)據(jù)
開始添加 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是一個Listview的接口用來確定在數(shù)據(jù)更新過程中那些行發(fā)生了變化。
看上面的代碼你也會注意到通過this.state來訪問dataSource,那么接下來就需要在constructor()構(gòu)造方法中創(chuàng)建一個空的dataSource。
現(xiàn)在通過dataSource來進(jìn)行存儲數(shù)據(jù),那么我們需要定義一個狀態(tài):this.state.loaded來確保獲取加載數(shù)據(jù)不出現(xiàn)重復(fù)請求存儲。
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
}
最后我們給ListView控件添加一個Style樣式風(fēng)格
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
萬里長征終于跨出了第一步~ 撒花~