React Native集成Redux框架讲解与应用

学过React Native的都知道,RN的UI是根据相应组件的state进行render的,而页面又是由大大小小的组件构成,导致每个组件都必须维护自身的一套状态,因此当页面复杂化的时候,管理state会相当吃力的。而redux提供了一套机制来组织管理整个应用状态。

       Redux有三部分组成:store,action,reducer。

       store:维护全局的state,以及将action和reducer结合起来。

       action:用来传递state的信息。(比如:我们在action中处理登陆操作,将返回的user对象传递给对应的reducer.)

       reducer:reducer是简单的处理函数,通过传入旧的state和指示操作的action来更新state,从而达到页面的刷新。

下面通过一个简单的例子来集成下。

首先安装相关库:

 

安装redux:npm install --save redux

安装redux绑定库:npm install --save react-redux

安装开发者工具:npm install --save-dev redux-devtools

安装异步action构造器:npm install --save redux-thunk

在集成之前熟悉下一般基于Redux的目录结构:

 


.
├── src #开发目录
| |
| ├──constants #ActionTypes和Urls
| |
| ├──actions #actions的文件
| |
| ├──components #内部组件
| |
| ├──containers #容器组件
| |
| ├──reducers #reducer文件
| |
| ├──stores #store配置文件
| |
| └──utils #工具
|
├── node_modules #包文件夹
├── .gitignore
├── index.js #入口文件
└── package.json

(如果你之前不了解Redux的话,或许会比较蒙圈,但不要紧,跟着流程多走几遍,试着推敲先分析下流程,慢慢就理解了)

/*************************************store*************************************/

1.首先创建全局的store。(一般在stores文件中写个配置文件)

 

[javascript] view plain copy
 
  1. 'use strict';  
  2.   
  3. import { createStore, applyMiddleware ,combineReducers} from 'redux';  
  4. import thunk from 'redux-thunk';//引入异步操作  
  5. //引入所有的reducers,切记要在index.js封装下.  
  6. import * as reducers from '../reducers';  
  7. const middlewares = [thunk];  
  8.   
  9. const createSoreWithMiddleware=applyMiddleware(...middlewares)(createStore);  
  10.   
  11. //配置store信息  
  12. export default function configureStore(initialState){  
  13.   
  14.   //将reducer组合起来  
  15.   const reducer=combineReducers(reducers);  
  16.   //创建store  
  17.   const store=createSoreWithMiddleware(reducer,initialState);  
  18.     
  19.   return store;  
  20. }  

简单讲解下:

首先引入该APP中所有的reducer,根据上面的目录结构,我们把所有的reducer放入到reducers文件中,切记要加入个index.js进行配置.上面很多都是固定格式,暂时先不分析为什么,做的目的就是返回一个全局的store.

store的应用(这里的APP就是我们应用的最顶层组件)。

 

[javascript] view plain copy
 
  1. import React, { Component } from 'react';  
  2. import {Provider} from 'react-redux';  
  3. import App from './containers/app';  
  4.   
  5. import configureStore from './store/configureStore';  
  6. const store=configureStore();//获取store  
  7.   
  8. export default class Root extends Component{  
  9.    render(){  
  10.      return(  
  11.        <Provider store={store}>  
  12.        <App/>  
  13.        </Provider>  
  14.      );  
  15.    }  
  16. }  

App组件其实可以把所有的页面加入到这里,全局进行控制(官方F8是这么操作的)。不过这里暂时先不这样处理,关于navigator的push,pop操作还是放到对应的页面进行处理,更符合我们的原生开发逻辑。

简单看下store中结构:

 

/*************************************action*************************************/

创建登陆对应的action:

 

[javascript] view plain copy
 
  1.  import * as types from './types';  
  2.  import {Alert}from 'react-native';  
  3.   
  4. //登陆(登陆操作属于耗时操作,所以需要异步执行,这里采用dispatch分发)  
  5. export function login(user){  
  6.    return dispatch=>{  
  7.      //登陆中,派遣给LOGIN_ING  
  8.      dispatch({type:types.LOGIN_ING});  
  9.      let result=fetch('http://www.baidu.com')  
  10.                 .then((res)=>{  
  11.                   //延时2s为了模拟效果  
  12.                   setTimeout(()=>{  
  13.                     if(user.phone=='15221850400'&&user.password=='123456'){  
  14.                       dispatch({type:types.LOGIN,user:user});  
  15.                     }else{  
  16.                       //这里分发的是action  
  17.                       Alert.alert('用户名或密码错误');  
  18.   
  19.                       dispatch(error());  
  20.                     }  
  21.                   },1000);  
  22.                 }).catch((err)=>{  
  23.                    alert(err);  
  24.                    dispatch({type:types.LOGIN_ERROR});  
  25.                 })  
  26.    }  
  27. }  
  28.   
  29. function error(){  
  30.   return {  
  31.     type:types.LOGIN_ERROR  
  32.   };  
  33. }  
  34.   
  35. //登出(由于登出操作一般都只是清空一些数据,不需要异步执行直接返回就可以了,)  
  36. export function logout(){  
  37.   return {  
  38.     type:types.LOGOUT,  
  39.   };  
  40. }  

逻辑还算简单,就只是做个用户名,密码判断,但或许会问dispatch是个什么玩意,哪来的呢,其实上面我们也截图出来了,这个方法是我们创建全局store中的方法。

至于action正常的应该只是一个含有type的json对象,但是为了扩展性,一般会写成函数的形式,俗称action creator如上面的logout方法.

至于login方法由于需要网络操作,固然是异步的,就好比我们原生开发的时候请求API的操作一般都会丢到一个线程中,通过Handler消息机制来渲染UI.

dispatch({type:types.LOGIN_ING}):根据相应的action来进行调用对应reducer方法。

/*************************************reducer*************************************/

接着我们看下最后一个reducer:

 

[javascript] view plain copy
 
  1. import * as types from '../actions/types';  
  2.   
  3. const initialState={  
  4.   isLoggedIn:false,//登陆状态  
  5.   user:{},  
  6.   status: null,//登陆操作状态 ‘done’:已登陆,'doing':正在登陆,null:没有登陆  
  7. };  
  8.   
  9. //reducer处理函数更新state,渲染UI(主要根据传入旧的state,)  
  10. export default function user(state=initialState,action={}){  
  11.   
  12.   switch(action.type) {  
  13.     case types.LOGIN:  
  14.        return{  
  15.          ...state,  
  16.          isLoggedIn:true,  
  17.          user:action.user,  
  18.          status: 'done',  
  19.        }  
  20.       break;  
  21.     case types.LOGIN_ING:  
  22.       return {  
  23.         ...state,  
  24.         isLoggedIn:false,  
  25.         status: 'doing',  
  26.       }  
  27.       break;  
  28.     case types.LOGIN_ERROR:  
  29.     console.log('types.LOGIN_ERROR...');  
  30.         return{  
  31.           ...state,  
  32.             isLoggedIn: false,  
  33.           status: null,  
  34.         };  
  35.         break;  
  36.     case types.LOGOUT:  
  37.   
  38.       return {  
  39.         ...state,  
  40.         isLoggedIn:false,  
  41.         status:null,  
  42.       }  
  43.       break;  
  44.     //切莫忘记default返回值  
  45.     default:  
  46.       return state;  
  47.   }  
  48. }  

reducer其实就是根据一系列action的处理函数,好比我们在前面action中返回的有LOGIN,LOGIN_ING,LOGIN_ERROR等状态,然后调用reducer根据不同的type返回当前最新的state,然后再render ui。

/*************************************connect*************************************/

redux的三部分至此就操作完毕,但如果进行链接起来呢,这里就用到connect组件,connect是将某一个组件(这里一般指一个页面)和store链接起来,目的就是获取当前页面所需的state以及dispatch方法。(从全局state中获取这个页面需要的数据然后以props的形式传递给当前页面。)

 

[javascript] view plain copy
 
  1. import React, { Component } from 'react';  
  2. import {  
  3.   StyleSheet,  
  4.   TextInput,  
  5.   Text,  
  6.   View,  
  7.   TouchableHighlight,  
  8.   ActivityIndicator,  
  9. } from 'react-native';  
  10.   
  11. import {connect} from 'react-redux';//将我们的页面和action链接起来  
  12. import {bindActionCreators} from 'redux';//将要绑定的actions和dispatch绑定到一起  
  13. import * as actionCreators from '../actions/loginActions';//导入需要绑定的actions  
  14. import Modal from 'react-native-modalbox';  
  15. import Home from './home';  
  16.   
  17.   
  18. /** 
  19. 登陆页面 
  20. **/  
  21. class Login extends Component{  
  22.   
  23.   constructor(props){  
  24.     super(props);  
  25.   
  26.     this.state={  
  27.     }  
  28.   
  29.     this.login=this.login.bind(this);  
  30.     this.onChangePhone=this.onChangePhone.bind(this);  
  31.     this.onChangePswd=this.onChangePswd.bind(this);  
  32.   }  
  33.   
  34.    onChangePhone(text){  
  35.      this.setState({'phone':text,});  
  36.    }  
  37.   
  38.    onChangePswd(text){  
  39.      this.setState({'password':text,});  
  40.    }  
  41.   
  42.    login(){  
  43.   
  44.      if(!this.state.phone||!this.state.password){  
  45.        alert('用户名或密码不能为空!');  
  46.      }else{  
  47.        this.refs.modal.open();//loading 状态  
  48.        this.props.actions.login({'phone':this.state.phone,'password':this.state.password});//dispath 登陆  
  49.      }  
  50.    }  
  51.   
  52.    //该方法首次不会执行,如果返回false,则reduer不会执行,,  
  53.    shouldComponentUpdate(nextProps,nextState){  
  54.      const {isLoggedIn,navigator}=nextProps;  
  55.       if(isLoggedIn){  
  56.         this.setState({phone:'',password:''});  
  57.   
  58.         navigator.push({  
  59.           component:Home,  
  60.           name:'Home',  
  61.         });  
  62.       }  
  63.      return true;  
  64.    }  
  65.   
  66.    render(){  
  67.     console.log('render...');  
  68.      return(  
  69.       <View style={{flex:1}}>  
  70.       <View style={{padding:20,marginTop:50}}>  
  71.       <View style={styles.item}><Text style={{width:70}}>手机号码</Text>  
  72.       <TextInput  
  73.       style={styles.input}  
  74.       onChangeText={this.onChangePhone}  
  75.       placeholder='请输入手机号码'  
  76.       value={this.state.phone}  
  77.       />  
  78.       </View>  
  79.       <View style={styles.item}>  
  80.       <Text style={{width:70}}>密码</Text>  
  81.       <TextInput  
  82.       style={styles.input}  
  83.       onChangeText={this.onChangePswd}  
  84.       placeholder='请输入密码'  
  85.       password={true}  
  86.       value={this.state.password}  
  87.       />  
  88.       </View>  
  89.   
  90.       <TouchableHighlight style={styles.button}  
  91.        underlayColor='#000000' onPress={this.login}>  
  92.       <Text style={{fontSize:16,color:'#fff'}}>登陆</Text>  
  93.       </TouchableHighlight>  
  94.       </View>  
  95.   
  96.       <Modal  
  97.       style={styles.modal}  
  98.       ref='modal'  
  99.       isOpen={this.props.status=='doing'?true:false}  
  100.       animationDuration={0}  
  101.       position={"center"}  
  102.       >  
  103.       <ActivityIndicator  
  104.       size='large'  
  105.       />  
  106.       <Text style={{marginTop:15,fontSize:16,color:'#444444'}}>登陆中...</Text>  
  107.       </Modal>  
  108.       </View>  
  109.      );  
  110.    }  
  111. }  
  112.   
  113. const styles =StyleSheet.create({  
  114.     item:{  
  115.       flex:1,  
  116.       flexDirection:'row',  
  117.       alignItems:'center',  
  118.       height:50,  
  119.       borderBottomColor:'#ddd',  
  120.       borderBottomWidth:1,  
  121.     },  
  122.     input:{  
  123.       flex:1,  
  124.       fontSize:14,  
  125.     },  
  126.     button:{  
  127.       backgroundColor:'#1a191f',  
  128.       height:50,  
  129.       marginTop:40,  
  130.       justifyContent:'center',  
  131.       alignItems:'center'  
  132.     },  
  133.     modal: {  
  134.       justifyContent: 'center',  
  135.       alignItems: 'center',  
  136.       width:150,  
  137.       height:150,  
  138.       borderRadius:10,  
  139.     },  
  140. });  
  141.   
  142. //根据全局state返回当前页面所需要的信息,(注意以props的形式传递给Login)  
  143. function mapStateToProps(state){  
  144.   return{  
  145.     isLoggedIn:state.user.isLoggedIn,  
  146.     status:state.user.status,  
  147.   };  
  148. }  
  149. //返回可以操作store.state的actions,(其实就是我们可以通过actions来调用我们绑定好的一系列方法)  
  150. function mapDispatchToProps(dispatch){  
  151.   return {  
  152.       actions: bindActionCreators(actionCreators, dispatch)  
  153.   };  
  154. }  
  155.   
  156. //链接起来  
  157. export default connect(mapStateToProps,mapDispatchToProps)(Login);  

上面的代码不用仔细看,主要是尾部的部分,

mapStateToProps方法:根据全局state返回当前页面所需的数据然后以props的形式传递给当前页面(Login)。

mapDispatchToProps:该方法就是将dispatch和当前页面引入的actionCreators绑定在一起,然后就可以轻松调用。

如:login方法中的:

this.props.actions.login({'phone':this.state.phone,'password':this.state.password});//dispath 登陆

 

这样整体集成就完毕了,这里简单总结下:

1.首先我们创建全局的store(基于所有的reducer)在APP最外层引用,然后我们创建action(可以根据页面或者某种类别来定义)。接着我们创建reducer(可以设计成跟action一一对应)。最后通过connect将它们和页面链接起来,至于action和reducer的内容,可以等页面编辑OK后再进行设计。

2.执行简单流程:在页面中首先调用对应action方法(传递参数)--->执行相应的业务逻辑,然后调用dispatch(action)(将结果以action的形式传递给reducer)--->在reducer中根据type字段然后返回最新的state,然后在进行render具体的ui。

 

引用原文:https://blog.csdn.net/jj120522/article/details/52071469

可以参考:https://www.jianshu.com/p/4139babc6d5e

 

写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,文章可以转载,无需版权。希望尽自己的努力,做到更好,大家一起努力进步!

如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!

posted @ 2018-03-22 20:36  緈諨の約錠  阅读(220)  评论(0编辑  收藏  举报
//用户复制动作发生0.1秒以后再去改粘贴板中的内容。100ms只是一个经常取值,写1000、10、50、200……都行。