首页完成效果展示:
<div style="text-align:center;"> <img src="https://img2018.cnblogs.com/blog/1312841/201907/1312841-20190718135309802-1875493361.gif" /> </div>一、开发占位图组件
在没有数据的时候使用占位图替代 items 的位置。
在 components 目录里创建 moviesItemPlaceholder.js
1import React, { Component } from 'react'; 2import { View, StyleSheet } from 'react-native'; 3import { px } from '../utils/device'; 4 5export default class MoviesItemPlaceholder extends Component { 6 render() { 7 const arr = [1, 2, 3, 4]; 8 return ( 9 <View style={styles.page}> 10 {arr.map((index) => ( 11 <View style={styles.placeholder} key={index}> 12 <View style={styles.img} /> 13 <View style={styles.title} /> 14 <View style={styles.rate} /> 15 </View> 16 ))} 17 </View> 18 ) 19 } 20} 21 22const styles = StyleSheet.create({ 23 page: { 24 flexDirection: 'row', 25 paddingLeft: px(30) 26 }, 27 placeholder: { 28 width: px(160), 29 marginRight: px(16) 30 }, 31 img: { 32 width: px(160), 33 height: px(224), 34 overflow: 'hidden', 35 borderRadius: px(8), 36 backgroundColor: '#f8f8f8' 37 }, 38 title: { 39 marginTop: px(20), 40 backgroundColor: '#f8f8f8', 41 height: px(30), 42 width: px(130), 43 overflow: 'hidden', 44 borderRadius: px(8) 45 }, 46 rate: { 47 marginTop: px(16), 48 backgroundColor: '#f8f8f8', 49 height: px(24), 50 width: px(130), 51 overflow: 'hidden', 52 borderRadius: px(8) 53 } 54});
二、首頁数据请求
使用 postman 之类的工具可以看到,首页接口返回的数据字段大致一样,数据均在 subject_collection_items 字段里,可以疯转一个方法量来请求数据。
1var items = ['showing', 'hot', 'tv', 'variety', 'book', 'music']; 2items.forEach(type => { 3 this.getList(type); 4}); 5getList = (type) => { 6 ajax(type, { 7 start: 0, 8 count: 9 9 }).then(value => { 10 let state = {} 11 state[type] = value.subject_collection_items; 12 this.setState(state); 13 }) 14 }
首页页面展示
纵向滑动,使用 ScrollView 组件;横向滑动,使用 FlatList 组件,FlatList 组件的 ListEmptyComponent 表示没有数据时显示的组件,在这里放置占位图组件;
1import React from "react"; 2import { View, Text, StatusBar, StyleSheet, ScrollView, FlatList, TouchableWithoutFeedback } from "react-native"; 3import { connect } from 'react-redux'; 4import ajax from "../utils/ajax"; 5import Header from '../components/header'; 6import ItemsHeader from '../components/itemsHeader'; 7import MoviesItem from '../components/moviesItem'; 8import MoviesItemPlaceholder from '../components/moviesItemPlaceholder'; 9import Icon from 'react-native-vector-icons/AntDesign'; 10import { px } from "../utils/device"; 11 12class Home extends React.Component { 13 constructor(props) { 14 super(props); 15 this.state = { 16 showing: [], 17 hot: [], 18 tv: [], 19 variety: [], 20 book: [], 21 music: [], 22 } 23 var items = ['showing', 'hot', 'tv', 'variety', 'book', 'music']; 24 items.forEach(type => { 25 this.getList(type); 26 }); 27 } 28 getList = (type) => { 29 ajax(type, { 30 start: 0, 31 count: 9 32 }).then(value => { 33 let state = {} 34 state[type] = value.subject_collection_items; 35 this.setState(state); 36 }) 37 } 38 render() { 39 const { dispatch, value, navigation } = this.props; 40 const { showing, hot, tv, variety, book, music } = this.state; 41 const sections = [ 42 { title: '影院热映', data: showing, type: 'showing' }, 43 { title: '豆瓣热门', data: hot, type: 'hot' }, 44 { title: '近期热门剧集', data: tv, type: 'tv' }, 45 { title: '近期热门综艺节目', data: variety, type: 'variety' }, 46 { title: '畅销图书', data: book, type: 'book' }, 47 { title: '热门单曲榜', data: music, type: 'music' } 48 ] 49 return ( 50 <View style={styles.page}> 51 <Header showBack={false} title='豆瓣评分' backgroundColor='#00b600' color='#fff' /> 52 <ScrollView> 53 <View style={styles.search}> 54 <TouchableWithoutFeedback onPress={() => alert('search')}> 55 <View style={styles.searchView}> 56 <Icon name='search1' size={px(30)} color='#ccc' /> 57 <Text style={styles.searchText}>搜索</Text> 58 </View> 59 </TouchableWithoutFeedback> 60 </View> 61 {sections.map((list, index) => ( 62 <View key={index} style={styles.list}> 63 <ItemsHeader title={list.title} onPress={() => navigation.push('List', { data: list })} /> 64 <FlatList 65 horizontal={true} 66 data={list.data} 67 keyExtractor={(item, index) => 'item' + index} 68 ListEmptyComponent={() => <MoviesItemPlaceholder />} 69 renderItem={({ item, index }) => ( 70 <View style={{ marginRight: index !== showing.length - 1 ? px(16) : px(30), marginLeft: index === 0 ? px(30) : 0 }}> 71 <MoviesItem data={item} /> 72 </View> 73 )} 74 /> 75 </View> 76 ))} 77 </ScrollView> 78 </View> 79 ); 80 } 81} 82 83const select = (store) => { 84 return { 85 value: store.num.value, 86 } 87} 88 89export default connect(select)(Home); 90 91const styles = StyleSheet.create({ 92 page: { 93 flex: 1, 94 backgroundColor: '#fff' 95 }, 96 search: { 97 backgroundColor: '#00b600', 98 height: px(80), 99 alignItems: 'center', 100 justifyContent: 'center' 101 }, 102 searchView: { 103 height: px(50), 104 width: px(710), 105 borderRadius: px(8), 106 backgroundColor: '#fff', 107 flexDirection: 'row', 108 justifyContent: 'center', 109 alignItems: 'center' 110 }, 111 searchText: { 112 fontSize: px(26), 113 color: '#ccc', 114 marginLeft: px(6) 115 }, 116 list: { 117 marginBottom: px(30) 118 } 119});
四、缓存列表数据
当处于弱网环境时,打开应用,可能会显示很久的占位图,此时我们可以将列表数据缓存至本地,每次进入应用先展示本地缓存的数据,然后请求数据,替换本地数据。
此时,就可以使用 redux 了。
编译 reducer
为了目录整清晰点(让 redux 相关的代码文件都存储于 store 目录下),将 src 目录下的 reducer 目录移动到 store 目录下,并在 reducer 目录创建 list.js。
1const initList = { 2 showing: [], 3 hot: [], 4 tv: [], 5 variety: [], 6 book: [], 7 music: [] 8} 9 10const setListState = (state = initList, action) => { 11 switch (action.type) { 12 case 'showing': 13 return { 14 ...state, 15 showing: action.data 16 } 17 case 'hot': 18 return { 19 ...state, 20 hot: action.data 21 } 22 case 'tv': 23 return { 24 ...state, 25 tv: action.data 26 } 27 case 'variety': 28 return { 29 ...state, 30 showing: action.data 31 } 32 case 'book': 33 return { 34 ...state, 35 book: action.data 36 } 37 case 'music': 38 return { 39 ...state, 40 music: action.data 41 } 42 default: 43 return state; 44 } 45} 46 47export default setListState;
在 reducer 的 index.js 中导入 setListState;
1... 2import setListState from './list'; 3... 4export default combineReducers({ 5 ... 6 list: setListState 7 ... 8});
修改 store/index.js 的路径引入
import reducer from './reducer';
编辑 action
将之前src下的 action 目录删除,在 store 目录下创建 action.js。
1export function login(data) { 2 return { 3 type: 'login', 4 data 5 } 6} 7 8export function logout(data) { 9 return { 10 type: 'logout', 11 data 12 } 13} 14 15// set 首页列表数据 16export function setShowing(data) { 17 return { 18 type: 'showing', 19 data 20 } 21} 22export function setHot(data) { 23 return { 24 type: 'hot', 25 data 26 } 27} 28export function setTv(data) { 29 return { 30 type: 'tv', 31 data 32 } 33} 34export function setVariety(data) { 35 return { 36 type: 'variety', 37 data 38 } 39} 40export function setBook(data) { 41 return { 42 type: 'book', 43 data 44 } 45} 46export function setMusic(data) { 47 return { 48 type: 'music', 49 data 50 } 51}
编辑首页
导入修改 store 的方法:
import { setShowing, setHot, setTv, setVariety, setBook, setMusic } from '../store/action';
页面获取 store 存储的数据:
1const select = (store) => { 2 return { 3 showing: store.list.showing, 4 hot: store.list.hot, 5 tv: store.list.tv, 6 variety: store.list.variety, 7 book: store.list.book, 8 music: store.list.music 9 } 10} 11export default connect(select)(Home);
页面获取数据时,改变 store 里的数据
由于需要 save 数据,所以前面创建的 getList 和传入的 items 需要做一些改变:
1... 2var items = [ 3 { type: 'showing', save: setShowing }, 4 { type: 'hot', save: setHot }, 5 { type: 'tv', save: setTv }, 6 { type: 'variety', save: setVariety }, 7 { type: 'book', save: setBook }, 8 { type: 'music', save: setmusic }, 9] 10items.forEach(item => { 11 this.getList(item); 12}); 13... 14getList = (item) => { 15 ajax(item.type, { 16 start: 0, 17 count: 9 18 }).then(value => { 19 this.props.dispatch(item.save(value.subject_collection_items)); 20 }) 21 }
修改 render 里的获取数据方式:
1render(){ 2 const { dispatch, value, navigation, showing, hot, tv, variety, book, music } = this.props; 3 ... 4}
至此,首页算是开发完了。