5. ListView应用

作者: kaenry | 来源:发表于2016-06-26 13:26 被阅读825次

    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
        }
    }
    
    

    可以看出只是在调用异步fetchdispatch我们真正要处理的函数,这样就可以延迟函数做到异步,这里尤其要注意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表示正在请求数据,此时应在主页面显示loadingmovies是请求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)
    

    这里需要注意的地方比较多:

    1. 调用action要使用dispatch
    2. const {isFetching, movies} = this.props;这个虽然在代码里没有显示声明,这是redux帮我们注入的
    3. ListViewdataSource要使用clone,这就像写C语言,不能随意修改指针一样的道理
    4. connect函数是redux提供的关键函数,详细请参考官方文档mapStateToProps的返回即redux需要注入的state,是要公开的属性,取决于你的state结构的设计,比如这里的dataSource由于是在组件内部使用,就没必要暴露给外界了
    5. 点击某个电影条目跳转到详情,为了简便起见,这里直接新建了一个detail.js内部使用WebView直接显示网页,实际效果图:
      Screenshot_20160626-131807.png
    Screenshot_20160626-132125.png

    github代码地址v0.0.3

    todo:添加Android手机Back事件

    相关文章

      网友评论

        本文标题:5. ListView应用

        本文链接:https://www.haomeiwen.com/subject/nxgadttx.html