ListView
大概是所有移动应用都会用到的组件了,大部分都在首页,这章结合redux
来看如何从API取数据再到如何应用redux
更新渲染组件ListView
。
书写redux
模式的异步请求API
- 新建app/comon/api.js,这里随便找的豆瓣电影的API做测试用,API接口详情请查看
'use strict'
const ApiHost = 'http://api.douban.com/v2/movie'
export default {
comming: `${ApiHost}/coming_soon`
}
- 新建app/home/constant.js,
action
常量
export const REQUEST_MOVIES = 'request_movies';
export const RECEIVE_MOVIES = 'receive_movies';
- 新建app/home/action.js,
action
一般在这里请求API做数据封装
'use strict'
import Api from '../common/api'
import Util from '../common/util'
import {
REQUEST_MOVIES, RECEIVE_MOVIES
} from './constant';
export function fetchMovies() {
return dispatch => {
// dispatch(fetchMovies())
fetch(Api.comming).then(ret => ret.json()).then((ret) => {
dispatch(receiveMovies(ret))
})
}
}
function fetchingMovies() {
return {
type: REQUEST_MOVIES,
isFetching: true,
}
}
function receiveMovies(ret) {
console.log('receive:',ret)
return {
type: RECEIVE_MOVIES,
isFetching: false,
movies: ret
}
}
可以看出只是在调用异步fetch
里dispatch
我们真正要处理的函数,这样就可以延迟函数做到异步,这里尤其要注意fetchMovies
函数是同步的,如果要异步执行,把fetch
返回即可,这里没有是以为没有必要,什么时候返回异步取决于你的业务以及你的state
的设计。
- 新建app/home/reducer.js,分发
action
'use strict'
import {
REQUEST_MOVIES, RECEIVE_MOVIES
} from './constant'
export function moviesReducer (
state={
isFetching: true,
movies: {},
}, action
) {
switch (action.type) {
case REQUEST_MOVIES:
return Object.assign({}, state, {
isFetching: true,
})
case RECEIVE_MOVIES:
return Object.assign({}, state, {
movies: action.movies,
isFetching: action.isFetching
})
default:
return state
}
}
初始化state
里有2个属性,isFetching
表示正在请求数据,此时应在主页面显示loading
,movies
是请求API获得的数据,方法体就是一个普通的switch
函数,不是一定要这样写,只要能正确处理返回即可,只有2点要求,修改state
时一定不能修改原来的state
,而是要返回新的,这里使用了Object.assign()
函数方便处理,也可以使用ES
语法新特性{...}
;另外一定要保证default
时返回旧的state
即可。
- 修改app/reducer.js,将新的
reducer
连接到主干上
import { combineReducers } from 'redux';
import route from './route';
import {moviesReducer} from './home/reducer';
export default appReducers = combineReducers({
route,
moviesReducer,
// other reducer
})
- 修改app/home/index.js,主要逻辑调用
import React, {
PropTypes,
} from 'react';
import {
View,
ScrollView,
Text,
Image,
ListView,
StyleSheet,
TouchableOpacity,
} from 'react-native';
import {connect} from 'react-redux'
import {Actions} from 'react-native-router-flux'
import {fetchMovies} from './action'
import Loading from '../components/loading'
import Util from '../common/util'
class Home extends React.Component {
constructor(props) {
super(props);
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
dataSource: ds
};
}
componentDidMount() {
const {dispatch} = this.props;
dispatch(fetchMovies())
}
render() {
console.log('render:',this.props)
const {isFetching, movies} = this.props;
if (isFetching) {
return <Loading />
}
return (
<ScrollView>
<View style={styles.listViewTitle}>
<Text>{movies.title}</Text>
</View>
<ListView dataSource={this.state.dataSource.cloneWithRows(movies.subjects)} renderRow={this._renderRow.bind(this)}
enableEmptySections={true}
bounces={false}
showsVerticalScrollIndicator={false}/>
</ScrollView>
);
}
_renderRow(row) {
const directors = row.directors;
return (
<TouchableOpacity style={styles.movieItem} onPress={() => Actions.detail({url: row.alt})}>
<View style={styles.movieAvatar}>
<Image source={{uri: row.images.large}} style={styles.avatarImage}/>
</View>
<View style={styles.movieInfo}>
<Text style={styles.movieTitle}>{row.original_title}</Text>
<Text>{row.genres.join(',')}</Text>
<Text>{row.pubdates}</Text>
<Text>导演:</Text>
{directors.map((v, key) => {return (<Text key={key}>{v.name}</Text>)})}
<Text style={{marginTop: 12}}>主演:</Text>
<Text>{row.casts.map((v, key) => {return v.name}).join('/')}</Text>
</View>
</TouchableOpacity>
)
}
}
Home.propTypes = {};
Home.defaultProps = {};
const styles = StyleSheet.create({
outerContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
listViewTitle: {
flex: 1,
alignItems: 'center',
},
movieItem: {
padding: 12,
flex: 2,
flexDirection: 'row',
},
avatarImage: {
width: Util.window.width/2 - 15,
height: Util.window.height/3,
},
movieInfo: {
paddingLeft: 12,
},
movieTitle: {
fontSize: 18,
color: '#8CD790',
width: Util.window.width/2 -12,
marginBottom: 15,
}
})
function mapStateToProps(state) {
return {
...state.moviesReducer
}
}
export default connect(mapStateToProps)(Home)
这里需要注意的地方比较多:
- 调用
action
要使用dispatch
-
const {isFetching, movies} = this.props;
这个虽然在代码里没有显示声明,这是redux
帮我们注入的 -
ListView
的dataSource
要使用clone,这就像写C语言,不能随意修改指针一样的道理 -
connect
函数是redux
提供的关键函数,详细请参考官方文档,mapStateToProps
的返回即redux
需要注入的state
,是要公开的属性,取决于你的state
结构的设计,比如这里的dataSource
由于是在组件内部使用,就没必要暴露给外界了 - 点击某个电影条目跳转到详情,为了简便起见,这里直接新建了一个
detail.js
内部使用WebView
直接显示网页,实际效果图:
github代码地址v0.0.3
todo:添加Android手机Back事件