react-native 的数据传递是父类传递给子类,子类通过this.props.** 读取数据,这样会造成组件多重嵌套,于是用redux可以更好的解决了数据和界面View之间的关系, 当然用到的是react-redux,是对redux的一种封装。
react基础的概念包括:
1.action是纯声明式的数据结构,只提供事件的所有要素,不提供逻辑,同时尽量减少在 action 中传递的数据
2. reducer是一个匹配函数,action的发送是全局的,所有的reducer都可以捕捉到并匹配与自己相关与否,相关就拿走action中的要素进行逻辑处理,修改store中的状态,不相关就不对state做处理原样返回。reducer里就是判断语句
3.Store 就是把以上两个联系到一起的对象,Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用reducer组合 而不是创建多个 store。
4.Provider是一个普通组件,可以作为顶层app的分发点,它只需要store属性就可以了。它会将state分发给所有被connect的组件,不管它在哪里,被嵌套多少层
5.connect一个科里化函数,意思是先接受两个参数(数据绑定mapStateToProps和事件绑mapDispatchToProps)再接受一个参数(将要绑定的组件本身)。mapStateToProps:构建好Redux系统的时候,它会被自动初始化,但是你的React组件并不知道它的存在,因此你需要分拣出你需要的Redux状态,所以你需要绑定一个函数,它的参数是state,简单返回你需要的数据,组件里读取还是用this.props.*
6.container只做component容器和props绑定, 负责输入显示出来,component通过用户的要交互调用action这样就完整的流程就如此
来张图
流程如上,那么结构如下。
需要实现的效果如下, 顶部 一个轮播,下面listview,底部导航切换,数据来源豆瓣电影
程序入口
import React, { Component } from 'react'; import { AppRegistry } from 'react-native'; import App from './app/app'; export default class movies extends Component { render() { return( <App/> ); } } AppRegistry.registerComponent('moives', () => moives)store 和数据初始化
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:38:09 * @modify date 2017-05-17 10:38:09 * @desc [description] */ import { createStore, applyMiddleware, compose } from 'redux'; import { Provider } from 'react-redux'; import thunk from 'redux-thunk' import React, { Component } from 'react'; import Root from './containers/root'; import allReducers from './reducers/allReducers'; import { initHotshow, fetchLoading } from './actions/hotshow-action'; const createStoreWithMiddleware = applyMiddleware(thunk)(createStore); const store = createStoreWithMiddleware(allReducers); //初始化 进入等待 首屏数据 ajax请求 store.dispatch(fetchLoading(true)); class App extends Component { constructor(props) { super(props); } render() { return ( <Provider store={ store }> <Root/> </Provider> ); } } module.exports = App;action纯函数,同时把action type单独写出来。在action同目录下文件types.js
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:36:44 * @modify date 2017-05-17 10:36:44 * @desc [description] */ 'use strict'; //首页 正在上映 export const HOTSHOW_BANNER = 'HOTSHOW_BANNER'; export const HOTSHOW_LIST = 'HOTSHOW_LIST'; export const HOTSHOW_FETCH = 'HOTSHOW_FETCH'; export const ADDMORE = 'AddMORE';hotshow-action.js
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-12 04:56:43 * @modify date 2017-05-12 04:56:43 * @desc [description] */ import { HOTSHOW_BANNER, HOTSHOW_LIST, HOTSHOW_FETCH, ADDMORE } from './types'; import { hotshowFetch } from '../middleware/index-api'; export const addBanner = (data) => { return { type: HOTSHOW_BANNER, data } } //加载等待,true 显示 反之 export const fetchLoading = (bool) => { return { type: HOTSHOW_FETCH, bool } } export const addList = (data) => { return { type: HOTSHOW_LIST, data } } // 正在热映 初始请求 export const initHotshow = () => { return hotshowFetch(addList); }
allReducers.js 整合所有reducer
import { combineReducers } from 'redux'; import { HotShowList, Banner, fetchLoading } from './hotshow/reducers' const allReducers = combineReducers({ hotshows: HotShowList, // 首屏数据列表 listview banner: Banner, // 轮播 fetchload: fetchLoading, //加载中boo }); export default allReducers;
hotshowreducer
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-12 04:56:34 * @modify date 2017-05-12 04:56:34 * @desc [description] */ import { HOTSHOW_BANNER, HOTSHOW_LIST, HOTSHOW_FETCH } from '../../actions/types'; export const HotShowList = (state = {}, action) => { switch (action.type) { case HOTSHOW_LIST: return Object.assign( {} , state , { data : action.data }); default: return state; } } export const Banner = (state = {}, action) => { switch (action.type) { case HOTSHOW_BANNER: let subjects = action.data; let data = subjects.slice(0, 5);// 前五个 return Object.assign( {} , state , { data : data }); default: return state; } } export const fetchLoading = (state = {}, action) => { switch (action.type) { case HOTSHOW_FETCH: return Object.assign( {} , state , { data : action.bool }); default: return state; } }
api 数据请求
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-16 08:34:36 * @modify date 2017-05-16 08:34:36 * @desc [description] */ //const hotshow = 'https://api.douban.com/v2/movie/in_theaters'; // const sonshow = 'https://api.douban.com/v2/movie/coming_soon'; // const usshow = 'https://api.douban.com/v2/movie/us_box'; // const nearcinemas = 'http://m.maoyan.com/cinemas.json'; const hotshow = 'http://192.168.×.9:8080/weixin/hotshow.json'; const sonshow = 'http://192.168.×.9:8080/weixin/sonshow.json'; const usshow = 'http://192.168.×.9:8080/weixin/usshow.json'; const nearcinemas = 'http://192.168.×.9:8080/weixin/nearcinemas.json'; import { initHotshow, fetchLoading } from '../actions/hotshow-action'; export function hotshowFetch(action) { return (dispatch) => { fetch(hotshow).then(res => res.json()) .then(json => { dispatch(action(json)); dispatch(fetchLoading(false)); }).catch(msg => console.log('hotshowList-err '+ msg)); } }
containers\hotshow\index
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-17 10:44:56 * @modify date 2017-05-17 10:44:56 * @desc [description] */ import React, { Component } from 'react'; import { View, ScrollView } from 'react-native'; import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import { size } from '../../util/style'; import HotShowList from './hotshow-list'; import Loading from '../../compoments/comm/loading' import { fetchLoading, initHotshow } from '../../actions/hotshow-action'; class hotshow extends Component { componentWillMount() { let _that = this; let time = setTimeout(function(){ //请求数据 _that.props.initHotshowAction(); clearTimeout(time); }, 1500); } render() { return (<View > {this.props.fetchbool ? <Loading/> : <HotShowList/> } </View>); } } function mapStateToProps(state) { return { fetchbool: state.fetchload.data, hotshows: state.hotshows.data } } function macthDispatchToProps(dispatch) { return bindActionCreators({ initHotshowAction: initHotshow, }, dispatch); } export default connect(mapStateToProps, macthDispatchToProps)(hotshow);BannerCtn 轮播用的swiper 插件, 但swiper加入 listview 有个bug就是图片不显示,结尾做答
import React, { Component } from 'react'; import { Text, StyleSheet, View, Image } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Swiper from 'react-native-swiper'; import { addBanner } from '../../actions/hotshow-action'; import { size } from '../../util/style'; class BannerCtn extends Component { render() { let data = this.props.banner.data; return ( <View style={{height: 200}}> { data !== undefined ? <Swiper height={200} autoplay={true}> { data.map((item, i) => { return ( <View key={i} style={{flex: 1, height:200}}> <Image style={{flex: 1}} resizeMode='cover' source={{uri: item.images.large}}/> <Text style={style.title}> {item.title} </Text> </View>) }) } </Swiper>: <Text>loading</Text> } </View> ); } } function mapStateToProps(state) { return { banner: state.banner } } let style = StyleSheet.create({ title: { position: 'absolute', width: size.width, bottom: 0, color: '#ffffff', textAlign: 'right', backgroundColor: 'rgba(230,69,51,0.25)' } }) export default connect(mapStateToProps)(BannerCtn);hotshow-list
import React, { Component } from 'react'; import { Text, View, ListView, StyleSheet } from 'react-native'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { addBanner } from '../../actions/hotshow-action'; import Loading from '../../compoments/comm/loading'; import Item from '../../compoments/hotshow/item'; import Banner from './banner-ctn'; import Foot from '../../compoments/comm/foot'; class HotShowList extends Component { constructor(props) { super(props); } componentWillMount() { //顶部轮播 let { hotshows, bannerAction } = this.props; let subs = hotshows.data.subjects; bannerAction(subs); } _renderList() { let { hotshows } = this.props; let ary = hotshows.data.subjects, subsAry = [], row=[]; row.push(<Banner/>); for(let i = 0, item; item = ary[i++];) { //一行两个 subsAry.push( <Item key={i} rank={i} data={item}/> ); if(subsAry.length == 2) { row.push(subsAry); subsAry = []; } } return row; } _renderRow(data) { return( <View style={{marginTop: 1, flexWrap:'wrap', flexDirection: 'row', justifyContent: 'space-between'}}>{data}</View> ); } render() { let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); let data = this._renderList(); this.state = { dataSource: ds.cloneWithRows(data), } //removeClippedSubviews 处理 banner 图片不显示 return ( <View> <View> <ListView removeClippedSubviews={false} dataSource={this.state.dataSource} renderRow={this._renderRow}/> </View> <Foot/> </View> ); } } function mapStateToProps(state) { return { hotshows: state.hotshows } } function macthDispatchToProps(dispatch) { return bindActionCreators({ bannerAction: addBanner}, dispatch); } let style = StyleSheet.create({ listbox: { marginBottom: 45, } }); export default connect(mapStateToProps, macthDispatchToProps)(HotShowList);剩下 便是foot tab 和单个item的编写
/** * @author ling * @email helloworld3q3q@gmail.com * @create date 2017-05-19 08:38:19 * @modify date 2017-05-19 08:38:19 * @desc [description] */ import React, { Component } from 'react'; import { Text, View, Image, StyleSheet } from 'react-native'; import { size } from '../../util/style'; const width = size.width/2-0.2; class item extends Component{ render() { let data = this.props.data; return( <View style={style.box}> <Image resizeMode='cover' style={style.avatar} source={{uri:data.images.large}}/> <View style={style.rank}> <Text style={style.rankTxt}>Top{this.props.rank}</Text> </View> <View style={style.msgbox}> <View style={style.msgrow}> <Text style={style.msgrowl}>{data.title}</Text> <Text style={style.msgrowr}>评分:{data.rating.average}</Text> </View> <View style={style.msgrow}> <Text style={style.msgrowl}> {data.genres.map((item, i)=> { if(i > 1) return; i == 1 ? null : item += ','; return item; })} </Text> <Text style={style.msgrowr}>观影人数:{data.collect_count}</Text> </View> </View> </View> ); } } let style = StyleSheet.create({ box: { width: width, paddingBottom: 1 }, avatar: { flex: 1, height: 260, }, rank: { position: 'absolute', top: 0, left: 0, backgroundColor: 'rgba(255,164,51,0.6)', paddingVertical: 1, paddingHorizontal: 3, borderBottomRightRadius: 4 }, rankTxt: { fontSize: 12, color: '#ffffff' }, msgbox: { position: 'absolute', bottom: 1, width: width, paddingHorizontal: 2, backgroundColor: 'rgba(230,69,51,0.5)', }, msgrow: { flex: 1, flexDirection: 'row', justifyContent: 'space-between', }, msgrowl: { fontSize: 12, color: '#ffffff' }, msgrowr: { fontSize: 13, color: '#ffffff' } }); module.exports = item;
Swiper Image 在ListView不显示在,解决如下,测试手机微android 4.4.4,有把react-native升级为0.44.2 亲测无效
constructor(props) { super(props); this.state={ visibleSwiper: false } } render() { let data = this.props.banner.data; return ( <View style={{height: 200}}> { this.state.visibleSwiper ? <Swiper/>: <Text>LOADING</Text> } </View> ); } componentDidMount() { let time = setTimeout(() => { this.setState({ visibleSwiper: true }); clearTimeout(time); }, 200); }
关于初始ajax数据,可以在create store的时候获取数据构建初始状态,也可以在ComponentDidMount的时候去执行action发ajax, 上文写错了,github 纠正
github:https://github.com/helloworld3q3q/react-native-redux-demo
有需要的交流的可以加个好友