三、 React Redux + React Navigation实现登录

目录
一、运行效果
二、React Redux
三、安装依赖
四、action
五、reduce
六、store
七、登录页面
八、登录与主页面切换

一、运行效果

Redux 是 JavaScript 状态容器,用来保存登录状态再好不过了。这次写的demo是一个登录页面,登录成功后跳转到tab主界面。React Navigation 5.0.0之前身份验证流程登录页面和主页面切换都是使用createSwitchNavigator,React Navigation 5.0.0之后就要判断登录的token进行页面自动切换。接下来看一下用React Redux和 React Navigation 5.0.7实现的登录效果:

登录.gif

二、React Redux

redux.jpg

随着 JavaScript 应用越来越大,越来越复杂,我们需要管理的state变得越来越多。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得非常复杂。

虽然React 试图在视图层禁止异步和直接操作 DOM 来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你。Redux就是为了帮你解决这个问题。

Redux主要有action、reducer、store三部分组成:
action:action就是一个描述发生什么的对象;
reducer:形式为 (state, action) => state 的纯函数,功能是根据action 修改state 将其转变成下一个 state;
store:用于存储state,你可以把它看成一个容器,整个应用只能有一个store。

Redux应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,action就是一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。

Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store;
redux一个特点是:状态共享,所有的状态都放在一个store中,任何component都可以订阅store中的数据;
并不是所有的state都适合放在store中,这样会让store变得非常庞大,如某个状态只被一个组件使用,不存在状态共享,可以不放在store中;

三、安装依赖

//redux 是 JavaScript 状态容器
npm install --save redux
// redux-thunk实现了异步Action的middleware;
npm install --save redux-thunk
//react-redux是方便开发者使用redux开发的一个开源库;
npm install --save react-redux
//(可选):支持store本地持久化;
npm install --save redux-persist
//(可选):实现可取消的action;
npm install --save redux-observable

四、action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源,也就是说要改变store中的state就需要触发一个action。

Action 本质上一个普通的JavaScript对象。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作,除了 type 字段外,action 对象的结构完全由你自己决定。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。

actionTypes:
 export const IS_Login = 'IS_Login'; 
action:
import * as actionTypes from './actionTypes'; 

export const actionLogin = (isLogin)=> ({
        type: actionTypes.IS_Login,
        isLogin
})
 

五、reduce

reducer是根据action 修改state 将其转变成下一个 state,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

import {IS_Login} from './actionTypes'
import { AsyncStorage } from "react-native"
import { useCallback } from 'react';
 
const defaultState = {
    isLogin : 'false',
}

export default (state = defaultState,action)=>{
    if(action.type === IS_Login){
        return { isLogin : action.isLogin} 
    }
    return state
}

六、store

store是存储state的容器,Store 会把两个参数(当前的 state 树和 action)传入 reducer。

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducers from './reducers';
 
const store = createStore(reducers,  applyMiddleware(thunk));
export default store ;

七、登录页面

import React, { Component } from 'react';
import { StyleSheet,Text, View,Image,Button,TextInput,Modal } from 'react-native';
import { connect } from 'react-redux';
import { deviceWidth, deviceHeight, setSpText ,scaleSize} from '../util/ScreenUtil';
import { actionLogin } from '../redux/action';
import { Url_Login } from '../util/UrlUtil';
import { TouchableOpacity } from 'react-native-gesture-handler';
import Spinkit from 'react-native-spinkit';
 
 class LoginView extends Component  {
  constructor(props) {
    super(props);

    this.state={
      spinkitVisible:false,
      spinkitSize:100,
      spinkitType:'FadingCircleAlt',
      spinkitColor:'#2C96F6',
      username:'',
      password:'',
    };
  }

   login=()=>{
    this.setState({
      spinkitVisible:true,
    });
    var formData = new FormData();
    formData.append('username', '18521496367');
    formData.append('password', '12345678');
    formData.append('rember', '1');
    formData.append('status', '1');

    fetch(Url_Login,{ method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: formData,
      credentials: 'include'
    }).then(response => response.json())
    .then(responseData => {
       if(responseData.err_code!=0){
         return;
      }
     
      if(responseData.data.status==101){
        this.props.loginChange();
      }
      this.setState({
        spinkitVisible:false,
      });
    });
  }

   render() {
      return (
        <View style={styles.container}>
            <Modal
               visible={this.state.spinkitVisible}
               transparent={true}>
                <View  style={{
                        flex: 1,
                        justifyContent: 'center',
                        alignItems: 'center',
                        backgroundColor: 'rgba(0, 0, 0, 0.6)'
                    }}>
                    <Spinkit   isVisible={this.state.spinkitVisible} 
                       size={this.state.spinkitSize} 
                       type={this.state.spinkitType} 
                       color={this.state.spinkitColor}/>
                </View>
            </Modal>
 
         <Image source={require('../images/login/login_top.png')} style={{width: deviceWidth, height: 320}} />
        
        <View style={styles.userName}> 
        <Image source={require('../images/login/login_username.png')} style={{width: 12, height: 18}} />
        <TextInput style={styles.userNameInput} placeholderTextColor={'#B3B1B2'} value='18521498866' placeholder='请输入用户名/手机号' />
        </View>

        <View style={styles.password}> 
        <Image source={require('../images/login/login_password.png')} style={{width: 15, height: 18}} />
        <TextInput style={styles.passwordInput} placeholderTextColor={'#B3B1B2'}  password='true' value='******' placeholder='请输入密码' />
        </View>

        <TouchableOpacity style={styles.loginBtn} onPress={this.login}>
          <Text style={styles.loginText} >登录</Text>
        </TouchableOpacity>
 
        <TouchableOpacity style={styles.registerBtn} onPress={() => this.props.navigation.navigate('RegisterView')}>
            <Text style={styles.registerText} >注册账号 {this.state.data}</Text>
        </TouchableOpacity>
 
        </View>
      );
    }
   }
 
  const styles = StyleSheet.create({
    container:{
      backgroundColor: 'white',
      height:deviceHeight,
    },
    userName:{
      marginTop:80,
      marginLeft:40,
      marginRight:40,
      width:deviceWidth - 80,
      height:40,
      borderBottomColor:'#B3B1B2',
      borderBottomWidth:1,
      flexDirection:'row',
      alignItems:'center'
    },
    userNameInput:{
      marginLeft:10,
      marginRight:10,
      height:40
    },
    password:{
      marginTop:20,
      marginLeft:40,
      marginRight:40,
      width:deviceWidth - 80,
      height:40,
      borderBottomColor:'#B3B1B2',
      borderBottomWidth:1,
      flexDirection:'row',
      alignItems:'center',
    },
    passwordInput:{
      marginLeft:10,
      marginRight:10,
      height:40
    },
    loginBtn:{
      marginTop:50,
      marginLeft:40,
      marginRight:40,
      height:44,
      backgroundColor: '#2C96F6',
      alignItems:'center',
      justifyContent:'center',
      textAlignVertical:'center',
      borderRadius:22,
    },
    loginText:{
      fontSize: 18,
      color: 'white',
    },
    registerBtn:{
      marginTop:10,
      marginLeft:40,
      marginRight:40,
      height:44,
      alignItems:'center',
      justifyContent:'center',
      textAlignVertical:'center',
    },
    registerText:{
      fontSize: 14,
      color: '#2C96F6',
    }
  });
 
  const dispatchToProps = (dispatch) =>{
    return {
      loginChange(){
        let action = actionLogin('true') ;
        dispatch(action);
    }
   }
  }
 
  export default connect(null,dispatchToProps)(LoginView);
 

八、登录与主页面切换


import React, { Component } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { connect } from 'react-redux';
 
import LoginView from './login/LoginView';
import RegisterView from './login/RegisterView';
import HomeView  from './home/HomeView';
import HomeDetail  from './home/HomeDetail';
import ServiceView  from './service/ServiceView';
import NoticeView  from './notice/NoticeView';
import MineView  from './mine/MineView';
  
class Main  extends Component {
    constructor(props) {
      super(props);
    
    }
  
   render() {
    const HomeStack = createStackNavigator();
    function HomeStackScreen() {
      return (
        <HomeStack.Navigator
        screenOptions={{
          headerStyle: {
            backgroundColor: '#2C96F6',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
          // headerBackImage:require("../images/icon/logo.png"),
          headerBackTitleVisible:false,                          
        }}
        >
          <HomeStack.Screen name="HomeView" component={HomeView}  options={{ title: '星星编程' }}/>
          <HomeStack.Screen name="HomeDetail" component={HomeDetail} options={{ title: '详情' }}/>
        </HomeStack.Navigator>
      );
    }
    
    const ServiceStack = createStackNavigator();
    function ServiceStackScreen() {
      return (
        <ServiceStack.Navigator
        screenOptions={{
          headerStyle: {
            backgroundColor: '#2C96F6',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
        >
          <ServiceStack.Screen name="ServiceView" component={ServiceView} options={{ title: '服务' }}/>
        </ServiceStack.Navigator>
      );
    }
     
    const NoticeStack = createStackNavigator();
    function NoticeStackScreen() {
      return (
        <NoticeStack.Navigator
        screenOptions={{
          headerStyle: {
            backgroundColor: '#2C96F6',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
        >
          <NoticeStack.Screen name="NoticeView" component={NoticeView} options={{ title: '通知' }}/>
        </NoticeStack.Navigator>
      );
    }
    
    const MineStack = createStackNavigator();
    function MineStackScreen() {
      return (
        <MineStack.Navigator
        screenOptions={{
          headerStyle: {
            backgroundColor: '#2C96F6',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        }}
        >
          <MineStack.Screen name="MineView" component={MineView} options={{ title: '我的' }}/>
        </MineStack.Navigator>
      );
    }
    
    const Tab = createBottomTabNavigator();
    function TabStackScreen() {
      return (
          <Tab.Navigator
           screenOptions={({ route }) => ({
            tabBarIcon: ({ focused, color, size }) => {
              let iconName='';
              if (route.name === 'Home') {
                iconName = focused  ? 'ios-home' : 'ios-home';
              }else if (route.name === 'Service') {
                iconName = focused ? 'ios-heart' : 'ios-heart';
              }else if (route.name === 'Notice') {
                iconName = focused ? 'ios-notifications' : 'ios-notifications';
              }else if (route.name === 'Mine') {
                iconName = focused ? 'md-person' : 'md-person';
              }
              return <Ionicons name={iconName} size={size} color={color} />;
            },
          })}
            tabBarOptions={{
              activeTintColor: '#2C96F6',
              inactiveTintColor: 'gray',
            }}
           >
            <Tab.Screen name="Home" component={HomeStackScreen} options={{ title: '首页' }}/>
            <Tab.Screen name="Service" component={ServiceStackScreen} options={{ title: '服务' }}/>
            <Tab.Screen name="Notice" component={NoticeStackScreen} options={{ title: '通知' }}/>
            <Tab.Screen name="Mine" component={MineStackScreen} options={{ title: '我的' }}/>
           </Tab.Navigator>  
      );
    }
    
    const LoginStack = createStackNavigator();
    function LoginStackScreen() {
      return (
        <LoginStack.Navigator
        screenOptions={{
            headerStyle: {
              backgroundColor: '#2C96F6',
            },
            headerTintColor: '#fff',
            headerTitleStyle: {
              fontWeight: 'bold',
            },
            // headerBackImage:require("../images/icon/logo.png"),
            headerBackTitleVisible:false,                          
          }}
        >
           <LoginStack.Screen
            name="LoginView"
            component={LoginView}
            options={{ headerShown: false }} 
          /> 
           <LoginStack.Screen
            name="RegisterView"
            component={RegisterView}
            options={{ title: '注册账号' }}
          />
        </LoginStack.Navigator>
      );
    }
     
     var mainView = <LoginStackScreen /> 
     if(this.props.isLogin == 'true'){
        mainView = <TabStackScreen/> 
     }
    
     return (
        <NavigationContainer>
          {mainView}
        </NavigationContainer>  
      );
    }
  }
  
  const stateToProps = (state)=>{
    return {
        isLogin : state.isLogin
    }
  }
   
  export default connect(stateToProps,null)(Main);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容