带有登录屏幕的 React-Navigation [英] React-Navigation with Login Screen

查看:21
本文介绍了带有登录屏幕的 React-Navigation的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 react-navigation 创建一个没有标签栏和标题的初始登录屏幕,一旦用户成功通过身份验证,将导航到另一个名为 LISTRECORD 的屏幕,它有一个标签栏、标题和没有后退按钮选项.任何人都有这方面的经验并可以分享吗?

总而言之,我试图通过 react-navigation 实现的目标描述如下...

屏幕 1:登录屏幕(无标题和标签栏)
已认证...
屏幕 2:LISTRECORD(标题、标签栏和无后退按钮)
标签栏也包含其他标签,用于导航到屏幕 3、屏幕 4...

解决方案

2017 年 10 月我发现这非常令人困惑,所以这是我从上到下的解决方案:

我建议开始一个新项目,然后将所有内容粘贴进去,然后再研究.我对代码进行了大量评论,因此如果您在任何特定领域遇到困难,也许上下文可以帮助您重回正轨.

<块引用>

这篇文章展示了如何:

  1. 完全设置 React Native 来运行 react-navigation
  2. 与 Redux 正确集成
  3. 处理 Android 后退按钮
  4. 嵌套堆栈导航器
  5. 从子导航器导航到父导航器
  6. 重置导航堆栈
  7. 在从子级导航到父级(嵌套)时重置导航堆栈

index.js

import { AppRegistry } from 'react-native'从'./src/App'导入应用程序AppRegistry.registerComponent('yourappname', () => App)

src/App.js(这是最重要的文件,因为它汇集了所有碎片)

import React, { Component } from 'react'//这将用于使您的 Android 硬件后退按钮工作从'react-native' 导入 { Platform, BackHandler }从'react-redux'导入{提供者,连接}从反应导航"导入 { addNavigationHelpers }//这是可以嵌套的最根最根导航堆栈//里面有你想要的尽可能多的栈从 './navigation/nav_reducer' 导入 { NavigationStack }//这是一个普通的商店//与 const store = createStore(combinedReducers) 相同从./store"导入商店//这会创建一个组件,并使用魔法带来导航堆栈//进入所有组件,并将其连接到 Redux//不要惹这个,否则你不会得到//this.props.navigation.navigate('somewhere') 随处可见//专业提示:这就是 addNavigationHelpers() 所做的//关键逻辑的后半部分将出现在 nav_reducers.js 文件中类 App 扩展组件 {//当应用程序被挂载时,为 Back Events 启动一个事件监听器//如果事件监听器返回false,则不会发生Back(注意)//经过一些测试,这似乎是最好的方法//返回始终工作,也永远不会关闭应用程序componentWillMount() {if (Platform.OS !== 'android') 返回BackHandler.addEventListener('hardwareBackPress', () => {const { dispatch } = this.propsdispatch({ type: '导航/返回' })返回真})}//当应用程序关闭时,移除事件监听器componentWillUnmount() {if (Platform.OS === 'android') BackHandler.removeEventListener('hardwareBackPress')}使成为() {//开启导航助手(关键步骤)const { dispatch, nav } = this.propsconst 导航 = addNavigationHelpers({派遣,状态:导航})return <NavigationStack navigation={navigation}/>}}//这里没什么疯狂的,只是将 Redux 状态映射到 <App/> 的 props;//然后我们创建您的根级组件准备好进行所有装饰const mapStateToProps = ({ nav }) =>({导航})const RootNavigationStack = 连接(mapStateToProps)(应用程序)const Root = () =>(<提供者商店={商店}><RootNavigationStack/></提供者>)导出默认根

src/navigation/nav_reducer.js

//NavigationActions 非常关键从反应导航"导入 { NavigationActions, StackNavigator }//这些实际上是您想要的任何标准组件//但是,它们位于堆栈的根部从'../components/Auth/Splash' 导入 Splash从'../components/Auth/SignupForm'导入注册从../components/Auth/LoginForm"导入登录从 '../components/Auth/ForgottenPassword' 导入 ForgottenPassword//这是嵌套视图的示例,您可能会在登录后看到import Dashboard from '../components/Dashboard'//index.js 文件const WeLoggedIn = StackNavigator({LandingPad: {//如果不指定初始路线,screen: Dashboard//第一个声明的首先加载}}, {标题模式:'无'initialRouteName: LandingPad//如果这个堆栈中有 5 个组件,})//当你这样做时会加载这个//this.props.navigation.navigate('WeLoggedIn')//注意我们正在导出这个.这变成了 <RootNavigationStack/>//在你的 src/App.js 文件中.导出 const NavigationStack = StackNavigator({飞溅:{屏幕:飞溅},注册: {屏幕:注册},登录: {屏幕:登录},忘记密码:{屏幕:忘记密码},我们登录:{screen: WeLoggedIn//注意屏幕是一个 StackNavigator}//现在你明白它是如何工作的了!}, {标题模式:'无'})//这对于与 Redux 一起玩的一切都非常重要//你有没有读过 React-Navigation 文档并记得它说的//大多数人没有正确连接它?好吧,你现在是正确的.//这是在初始化时将您的状态正确转换为 Reduxconst INITIAL_STATE = NavigationStack.router.getStateForAction(NavigationActions.init())//这几乎是一个标准的减速器,但它看起来很花哨//它只关心导航堆栈是否改变了?";//如果是 =>更新堆栈//如果没有 =>通过当前堆栈导出默认值(状态 = INITIAL_STATE,动作)=>{const nextState = NavigationStack.router.getStateForAction(action, state)返回下一个状态 ||状态}

src/store/index.js

//记得我说过这只是一个标准的商店//这个稍微高级一点给你看import { createStore, compose, applyMiddleware } from 'redux'从redux-thunk"导入 thunk从'redux-persist'导入{persistStore,autoRehydrate}import { AsyncStorage } from 'react-native'//这会引入你的组合减速器//nav_reducer 就是其中之一从'../reducers'导入减速器const store = createStore(减速机,{},撰写(应用中间件(thunk),自动补水()))persistStore(store, { storage: AsyncStorage, whitelist: [] })//这将它导出到 App.js导出默认存储

src/reducers.js

//这是我的 reducers 文件.我不想有任何混乱从redux"导入 { combineReducers }//这是一个标准的减速器,和你从幼儿园开始使用的一样//使用 LOGIN_SUCCESS, LOGIN_FAIL 等操作类型从 './components/Auth/login_reducer' 导入 loginReducer从 './navigation/nav_reducer' 导入 navReducer导出默认的 combineReducers({身份验证:loginReducer,导航:导航减速器})

src/components/Auth/SignUpForm.js

我将在这里向您展示一个示例.这不是我的,我只是在这个摇摇欲坠的 StackOverflow 编辑器中为你打出来的.如果你喜欢它,请给我竖起大拇指:)

import React, { Component } from 'react'import { View, Text, TouchableOpacity } from 'react-native//注意 this.props.navigation 是如何工作的,没有 mapStateToProps//一些巫师做了这个,不是我类注册扩展组件{使成为() {返回 (<查看><文本>注册</文本><TouchableOpacity onPress={() =>this.props.navigation.navigate('Login')}><文本>转到登录视图</文本></TouchableOpacity></查看>)}}导出默认注册

src/components/Auth/LoginForm.js

我也会向您展示一个带有超级涂料后退按钮的愚蠢风格

从'react'导入Reactimport { View, Text, TouchableOpacity } from 'react-native//注意我们是如何传入导航的const SignIn = ({导航}) =>{返回 (<查看><Text>登录</Text><TouchableOpacity onPress={() =>navigation.goBack(null)}><Text>返回注册视图</Text></TouchableOpacity></查看>)}导出默认登录

src/components/Auth/Splash.js

这是一个您可以玩的启动画面.我像使用高阶组件一样使用它:

import React, { Component } from 'react'import { StyleSheet, View, Image, Text } from 'react-native'//https://github.com/oblador/react-native-animatable//这是一个你真的应该使用的库import * as Animatable from 'react-native-animatable'从'react-redux'导入{连接}从 './login_actions' 导入 { initializeApp }类 Splash 扩展组件 {构造函数(道具){超级(道具)this.state = {}}componentWillMount() {setTimeout(() => this.props.initializeApp(), 2000)}componentWillReceiveProps(nextProps) {//if (!nextProps.authenticated) this.props.navigation.navigate('Login')if (nextProps.authenticated) this.props.navigation.navigate('WeLoggedIn')}使成为() {const { 容器、图像、文本 } = 样式返回 (<视图样式={容器}><图像样式={图像}source={require('./logo.png')}/><Animatable.Text样式={文本}持续时间={1500}动画=橡皮筋"缓动=线性"迭代计数=无限">正在加载...</Animatable.Text><文本>{(this.props.authenticated) ?登录":未登录"}</Text></查看>)}}const 样式 = StyleSheet.create({容器: {弹性:1,justifyContent: '中心',alignItems: '中心',背景颜色:'#F0F0F0'},图像: {高度:110,调整大小模式:'包含'},文本: {边距顶部:50,字体大小:15,颜色:'#1A1A1A'}})//我的 LOGIN_SUCCESS 动作创建者将 state.auth.isAuthenticated 翻转为 true//所以这个启动画面只是看着它const mapStateToProps = ({ auth }) =>{返回 {已认证:auth.isAuthenticated}}导出默认连接(mapStateToProps,{ initializeApp })(飞溅)

src/components/Auth/login_actions.js

我将向您展示 initializeApp() 以便您了解一些想法:

import {初始化_APP,CHECK_REMEMBER_ME,TOGGLE_REMEMBER_ME,登录_初始化,登录_成功,登录失败,登出} 来自'./login_types'//初始化应用//这还没有完成,没有 try/catch 和 LOGIN_FAIL 没有连接//但你明白了//如果检测到有效的 JWT,它们将被导航到 WeLoggedIn导出 const initializeApp = () =>{返回异步(调度)=>{dispatch({ type: INITIALIZE_APP })const user = await AsyncStorage.getItem('token').catch((error) => dispatch({ type: LOGIN_FAIL, payload: error }))if (!user) return dispatch({ type: LOGIN_FAIL, payload: 'No Token' })返回调度({类型:登录_成功,有效载荷:用户})//navigation.navigate('WeLoggedIn')//如果需要,将导航传递到此函数中}}

在其他用例中,您可能更喜欢高阶组件.它们的工作方式与 React for web 完全相同.Stephen Grider 关于 Udemy 的教程是最好的,时期.

src/HOC/require_auth.js

import React, { Component } from 'react'从'react-redux'导入{连接}导出默认函数(ComposedComponent){类身份验证扩展组件{componentWillMount() {if (!this.props.authenticated) this.props.navigation.navigate('Login')}componentWillUpdate(nextProps) {if (!nextProps.authenticated) this.props.navigation.navigate('Login')}使成为() {返回 (<ComposedComponent {...this.props}/>)}}const mapStateToProps = ({ auth }) =>{返回 {已认证:auth.isAuthenticated}}返回连接(mapStateToProps)(身份验证)}

你就像这样使用它:

从 '../HOC/require_auth' 导入 requireAuth类 RestrictedArea 扩展组件 {//... 普通视图组件}

//将状态映射到道具

导出默认连接(mapStateToProps, actions)(requireAuth(RestrictedArea))

这就是我希望有人告诉我并展示给我的一切.

<块引用>

TLDR App.jsnav_reducer.js 文件绝对是最重要的.其余的都是熟悉的.我的例子应该会让你加速成为一台野蛮的生产力机器.

这是我的注销操作创建者.如果您希望清除导航堆栈,您会发现它非常有用,这样用户就无法按下 Android 硬件后退按钮并返回到需要身份验证的屏幕:

//LOGOUTexport const onLogout = (导航) =>{返回异步(调度)=>{尝试 {等待 AsyncStorage.removeItem('token')导航.调度({类型:'导航/重置',指数:0,动作:[{ type: 'Navigate', routeName: 'Login' }]})返回调度({类型:注销})} 捕捉(错误){//无错误地让用户通过//这会恢复 INITIAL_STATE(参见 login_reducer.js)返回调度({类型:注销})}}}//login_reducer.js案例注销:{返回 {...INITIAL_STATE,isAuthenticated: 假,}}

<块引用>

[额外编辑] 如何从子 Stack Navigator 导航到父 Stack Navigator?

如果您想从您的子堆栈导航器之一导航并重置堆栈,请执行以下操作:

  1. 在添加代码的组件中,您可以在其中使用 this.props.navigation
  2. 制作一个类似
  3. 的组件
  4. 将导航传递给它,就像这样:<Something navigation={this.props.navigation}/>
  5. 进入该组件的代码
  6. 注意你如何在这个子组件中使用 this.props.navigation
  7. 现在你已经完成了,只需调用 this.props.navigation.navigate('OtherStackScreen') 并且你应该看到 React Native 神奇地去那里没有问题

<块引用>

但是,我想在导航到父堆栈时重置整个堆栈.

  1. 调用动作创建者或类似的东西(从第 6 步开始):this.props.handleSubmit(data, this.props.navigation)
  2. 进入操作创建器并观察可能存在的这段代码:

actionCreators.js

//我们需要它来在重置时正确地从子导航器转到父导航器//如果您从子导航器执行正常的重置方法:this.props.navigation.dispatch({类型:'导航/重置',指数:0,动作:[{ type: 'Navigate', routeName: 'SomeRootScreen' }]})//你会看到一个关于大红色错误消息的错误和//屏幕必须在您当前的堆栈中//别担心,我支持你.做这个//(记住,这是在动作创建者的上下文中):从反应导航"导入 { NavigationActions }//注意我们是如何从组件传入 this.props.navigation 的,//所以我们可以称它为 Dan Abramov 与 Gandolf 的混合export const handleSubmit = (token, navigation) =>异步(调度)=>{尝试 {//让我们用令牌做一些操作await AsyncStorage.setItem('token@E1', token)//让我们分派一些本身不会导致导航的动作//如果遇到麻烦,调查 shouldComponentUpdate()//如果此时检测到此动作,则返回false调度({ 类型:某事_完成})//这里是 100% 疯狂和令人振奋的地方返回 navigation.dispatch(NavigationActions.reset({//这表示把它放在索引 0 上,也就是栈顶指数:0,//这个键:null 是 9001% 关键,这就是//实际擦除堆栈键:空,//这会将您导航到根导航堆栈中的某个屏幕动作:[NavigationActions.navigate({ routeName: 'SomeRootScreen' })]}))} 捕捉(错误){调度({ 类型:某事_完成})//如果令牌保存失败,用户应手动登录返回 navigation.dispatch(NavigationActions.reset({指数:0,键:空,动作:[NavigationActions.navigate({ routeName: 'Login' })]}))}}

我在企业级 React Native 应用程序中使用此代码,并且运行良好.

react-navigation 就像函数式编程.它被设计为在小型纯导航"中处理.很好地组合在一起的片段.如果您采用上述策略,您会发现自己创建了可重复使用的导航逻辑,您可以根据需要粘贴这些逻辑.

I am trying to use react-navigation to create a initial LOGIN screen which has no tabbar and header, and once the user has been successfully authenticated will navigate to another screen called LISTRECORD which has a tabbar, header and no back button option. Anyone has experience in this and can share?

In summary, what i m trying to achieve with react-navigation is described below...

Screen 1: Login Screen (No Header & Tabbar)
Authenticated...
Screen 2: LISTRECORD (Header, Tabbar and No Back Button)
The tabbar contains other tabs too for navigation to Screen 3, Screen 4...

解决方案

Oct 2017 I found this ridiculously confusing, so here is my solution starting from the top down:

I recommend starting a new project and literally just paste all this in and study it after. I commented the code big-time, so if you are stuck on any specific area, maybe the context can help you get back on track.

This post shows how to:

  1. completely setup React Native to run react-navigation
  2. Properly integrate with Redux
  3. Handle Android Back Button
  4. Nest Stack Navigators
  5. Navigate from child to parent navigators
  6. Reset the Navigation Stack
  7. Reset the Navigation Stack while navigating from child to parent (nested)

index.js

import { AppRegistry } from 'react-native'
import App from './src/App'

AppRegistry.registerComponent('yourappname', () => App)

src/App.js (this is the most important file because it brings all the shreds together)

import React, { Component } from 'react'
// this will be used to make your Android hardware Back Button work
import { Platform, BackHandler } from 'react-native'
import { Provider, connect } from 'react-redux'
import { addNavigationHelpers } from 'react-navigation'
// this is your root-most navigation stack that can nest
// as many stacks as you want inside it
import { NavigationStack } from './navigation/nav_reducer'
// this is a plain ol' store
// same as const store = createStore(combinedReducers)
import store from './store'

// this creates a component, and uses magic to bring the navigation stack
// into all your components, and connects it to Redux
// don't mess with this or you won't get
// this.props.navigation.navigate('somewhere') everywhere you want it
// pro tip: that's what addNavigationHelpers() does
// the second half of the critical logic is coming up next in the nav_reducers.js file
class App extends Component {
    // when the app is mounted, fire up an event listener for Back Events
    // if the event listener returns false, Back will not occur (note that)
    // after some testing, this seems to be the best way to make
    // back always work and also never close the app
    componentWillMount() {
        if (Platform.OS !== 'android') return
        BackHandler.addEventListener('hardwareBackPress', () => {
            const { dispatch } = this.props
            dispatch({ type: 'Navigation/BACK' })
            return true
        })
    }

    // when the app is closed, remove the event listener
    componentWillUnmount() {
        if (Platform.OS === 'android') BackHandler.removeEventListener('hardwareBackPress')
    }

    render() {
        // slap the navigation helpers on (critical step)
        const { dispatch, nav } = this.props
        const navigation = addNavigationHelpers({
            dispatch,
            state: nav
        })
        return <NavigationStack navigation={navigation} />
    }
}

// nothing crazy here, just mapping Redux state to props for <App />
// then we create your root-level component ready to get all decorated up
const mapStateToProps = ({ nav }) => ({ nav })
const RootNavigationStack = connect(mapStateToProps)(App)

const Root = () => (
    <Provider store={store}>
        <RootNavigationStack />
    </Provider>
)

export default Root

src/navigation/nav_reducer.js

// NavigationActions is super critical
import { NavigationActions, StackNavigator } from 'react-navigation'
// these are literally whatever you want, standard components
// but, they are sitting in the root of the stack
import Splash from '../components/Auth/Splash'
import SignUp from '../components/Auth/SignupForm'
import SignIn from '../components/Auth/LoginForm'
import ForgottenPassword from '../components/Auth/ForgottenPassword'
// this is an example of a nested view, you might see after logging in
import Dashboard from '../components/Dashboard' // index.js file

const WeLoggedIn = StackNavigator({
    LandingPad: {             // if you don't specify an initial route,
        screen: Dashboard     // the first-declared one loads first
    }
}, {
    headerMode: 'none'
    initialRouteName: LandingPad // if you had 5 components in this stack,
})                               // this one would load when you do
                                 // this.props.navigation.navigate('WeLoggedIn')

// notice we are exporting this one. this turns into <RootNavigationStack />
// in your src/App.js file.
export const NavigationStack = StackNavigator({
    Splash: {
        screen: Splash
    },
    Signup: {
        screen: SignUp
    },
    Login: {
        screen: SignIn
    },
    ForgottenPassword: {
        screen: ForgottenPassword
    },
    WeLoggedIn: {
        screen: WeLoggedIn  // Notice how the screen is a StackNavigator
    }                       // now you understand how it works!
}, {
    headerMode: 'none'
})

// this is super critical for everything playing nice with Redux
// did you read the React-Navigation docs and recall when it said
// most people don't hook it up correctly? well, yours is now correct.
// this is translating your state properly into Redux on initialization    
const INITIAL_STATE = NavigationStack.router.getStateForAction(NavigationActions.init())

// this is pretty much a standard reducer, but it looks fancy
// all it cares about is "did the navigation stack change?"    
// if yes => update the stack
// if no => pass current stack through
export default (state = INITIAL_STATE, action) => {
    const nextState = NavigationStack.router.getStateForAction(action, state)

    return nextState || state
}

src/store/index.js

// remember when I said this is just a standard store
// this one is a little more advanced to show you
import { createStore, compose, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { persistStore, autoRehydrate } from 'redux-persist'
import { AsyncStorage } from 'react-native'
// this pulls in your combinedReducers
// nav_reducer is one of them
import reducers from '../reducers'

const store = createStore(
    reducers,
    {},
    compose(
        applyMiddleware(thunk),
        autoRehydrate()
    )
)

persistStore(store, { storage: AsyncStorage, whitelist: [] })

// this exports it for App.js    
export default store

src/reducers.js

// here is my reducers file. I don't want any confusion
import { combineReducers } from 'redux'
// this is a standard reducer, same as you've been using since kindergarten
// with action types like LOGIN_SUCCESS, LOGIN_FAIL
import loginReducer from './components/Auth/login_reducer'
import navReducer from './navigation/nav_reducer'

export default combineReducers({
    auth: loginReducer,
    nav: navReducer
})

src/components/Auth/SignUpForm.js

I will show you a sample here. This isn't mine, I just typed it out for you in this rickety StackOverflow editor. Please give me thumbs up if you appreciate it :)

import React, { Component } from 'react'
import { View, Text, TouchableOpacity } from 'react-native

// notice how this.props.navigation just works, no mapStateToProps
// some wizards made this, not me
class SignUp extends Component {
    render() {
        return (
            <View>
                <Text>Signup</Text>
                <TouchableOpacity onPress={() => this.props.navigation.navigate('Login')}>
                    <Text>Go to Login View</Text>
                </TouchableOpacity>
            </View>
        )
    }
}

export default SignUp

src/components/Auth/LoginForm.js

I'll show you a dumb style one also, with the super dope back button

import React from 'react'
import { View, Text, TouchableOpacity } from 'react-native

// notice how we pass navigation in
const SignIn = ({ navigation }) => {
    return (
        <View>
            <Text>Log in</Text>
            <TouchableOpacity onPress={() => navigation.goBack(null)}>
                <Text>Go back to Sign up View</Text>
            </TouchableOpacity>
        </View>
    )
}

export default SignIn

src/components/Auth/Splash.js

Here is a splash screen you can play around with. I am using it like a higher-order component:

import React, { Component } from 'react'
import { StyleSheet, View, Image, Text } from 'react-native'
// https://github.com/oblador/react-native-animatable
// this is a library you REALLY should be using
import * as Animatable from 'react-native-animatable' 
import { connect } from 'react-redux'
import { initializeApp } from './login_actions'

class Splash extends Component {
    constructor(props) {
        super(props)
        this.state = {}
    }

    componentWillMount() {
        setTimeout(() => this.props.initializeApp(), 2000)
    }

    componentWillReceiveProps(nextProps) {
        // if (!nextProps.authenticated) this.props.navigation.navigate('Login')
        if (nextProps.authenticated) this.props.navigation.navigate('WeLoggedIn')
    }

    render() {
        const { container, image, text } = styles
        return (
            <View style={container}>
                    <Image
                        style={image}
                        source={require('./logo.png')}
                    />

                    <Animatable.Text
                        style={text}
                        duration={1500}
                        animation="rubberBand"
                        easing="linear"
                        iterationCount="infinite"
                    >
                        Loading...
                    </Animatable.Text>
                    <Text>{(this.props.authenticated) ? 'LOGGED IN' : 'NOT LOGGED IN'}</Text>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F0F0F0'
    },
    image: {
        height: 110,
        resizeMode: 'contain'
    },
    text: {
        marginTop: 50,
        fontSize: 15,
        color: '#1A1A1A'
    }
})

// my LOGIN_SUCCESS action creator flips state.auth.isAuthenticated to true    
// so this splash screen just watches it
const mapStateToProps = ({ auth }) => {
    return {
        authenticated: auth.isAuthenticated
    }
}

export default connect(mapStateToProps, { initializeApp })(Splash)

src/components/Auth/login_actions.js

I'm just going to show you initializeApp() so you get some ideas:

import {
    INITIALIZE_APP,
    CHECK_REMEMBER_ME,
    TOGGLE_REMEMBER_ME,
    LOGIN_INITIALIZE,
    LOGIN_SUCCESS,
    LOGIN_FAIL,
    LOGOUT
} from './login_types'

//INITIALIZE APP
// this isn't done, no try/catch and LOGIN_FAIL isn't hooked up
// but you get the idea
// if a valid JWT is detected, they will be navigated to WeLoggedIn
export const initializeApp = () => {
    return async (dispatch) => {
        dispatch({ type: INITIALIZE_APP })

        const user = await AsyncStorage.getItem('token')
            .catch((error) => dispatch({ type: LOGIN_FAIL, payload: error }))

        if (!user) return dispatch({ type: LOGIN_FAIL, payload: 'No Token' })

        return dispatch({
            type: LOGIN_SUCCESS,
            payload: user
        })
        // navigation.navigate('WeLoggedIn')
        // pass navigation into this function if you want
    }
}

In other use cases, you may prefer the higher-order component. They work exactly the same as React for web. Stephen Grider's tutorials on Udemy are the best, period.

src/HOC/require_auth.js

import React, { Component } from 'react'
import { connect } from 'react-redux'

export default function (ComposedComponent) {
    class Authentication extends Component {

        componentWillMount() {
            if (!this.props.authenticated) this.props.navigation.navigate('Login')
        }

        componentWillUpdate(nextProps) {
            if (!nextProps.authenticated) this.props.navigation.navigate('Login')
        }

        render() {
            return (
                <ComposedComponent {...this.props} />
            )
        }
    }

    const mapStateToProps = ({ auth }) => {
        return {
            authenticated: auth.isAuthenticated
        }
    }

    return connect(mapStateToProps)(Authentication)
}

You use it just like this:

import requireAuth from '../HOC/require_auth'

class RestrictedArea extends Component {
    // ... normal view component
}

//map state to props

export default connect(mapStateToProps, actions)(requireAuth(RestrictedArea))

There, that is everything I wish someone told and showed me.

TLDR The App.js, and nav_reducer.js files are absolutely the most important to get right. The rest is old familiar. My examples should accelerate you into a savage productivity machine.

[Edit] Here is my logout action creator. You will find it very useful if you wish to wipe off your navigation stack so the user cannot press Android Hardware Back Button and go back to a screen that requires authentication:

//LOGOUT
export const onLogout = (navigation) => {
    return async (dispatch) => {
        try {
            await AsyncStorage.removeItem('token')

            navigation.dispatch({
                type: 'Navigation/RESET',
                index: 0,
                actions: [{ type: 'Navigate', routeName: 'Login' }]
            })

            return dispatch({ type: LOGOUT })
        } catch (errors) {
            // pass the user through with no error
            // this restores INITIAL_STATE (see login_reducer.js)
            return dispatch({ type: LOGOUT })
        }
    }
}

// login_reducer.js
case LOGOUT: {
    return {
        ...INITIAL_STATE,
        isAuthenticated: false,
    }
}

[bonus edit] How do I navigate from a child Stack Navigator to a parent Stack Navigator?

If you want to navigate from one of your child Stack Navigators and reset the stack, do this:

  1. Be inside a component adding code, where you have this.props.navigation available
  2. Make a component like <Something />
  3. Pass navigation into it, like this: <Something navigation={this.props.navigation} />
  4. Go into the code for that component
  5. Notice how you have this.props.navigation available inside this child component
  6. Now you're done, just call this.props.navigation.navigate('OtherStackScreen') and you should watch React Native magically go there without problem

But, I want to RESET the whole stack while navigating to a parent stack.

  1. Call an action creator or something like this (starting off from step 6): this.props.handleSubmit(data, this.props.navigation)
  2. Go into the action creator and observe this code that could be there:

actionCreators.js

// we need this to properly go from child to parent navigator while resetting
// if you do the normal reset method from a child navigator:
this.props.navigation.dispatch({
    type: 'Navigation/RESET',
    index: 0,
    actions: [{ type: 'Navigate', routeName: 'SomeRootScreen' }]
})

// you will see an error about big red error message and
// screen must be in your current stack 
// don't worry, I got your back. do this
// (remember, this is in the context of an action creator):
import { NavigationActions } from 'react-navigation'

// notice how we passed in this.props.navigation from the component,
// so we can just call it like Dan Abramov mixed with Gandolf
export const handleSubmit = (token, navigation) => async (dispatch) => {
    try {
        // lets do some operation with the token
        await AsyncStorage.setItem('token@E1', token)
        // let's dispatch some action that doesn't itself cause navigation
        // if you get into trouble, investigate shouldComponentUpdate()
        // and make it return false if it detects this action at this moment
        dispatch({ type: SOMETHING_COMPLETE })

        // heres where it gets 100% crazy and exhilarating
        return navigation.dispatch(NavigationActions.reset({
            // this says put it on index 0, aka top of stack
            index: 0,
            // this key: null is 9001% critical, this is what
            // actually wipes the stack
            key: null,
            // this navigates you to some screen that is in the Root Navigation Stack
            actions: [NavigationActions.navigate({ routeName: 'SomeRootScreen' })]
        }))
    } catch (error) {
        dispatch({ type: SOMETHING_COMPLETE })
        // User should login manually if token fails to save
        return navigation.dispatch(NavigationActions.reset({
            index: 0,
            key: null,
            actions: [NavigationActions.navigate({ routeName: 'Login' })]
        }))
    }
}

I am using this code inside an enterprise-grade React Native app, and it works beautifully.

react-navigation is like functional programming. It is designed to be handled in small "pure navigation" fragments that compose well together. If you employ the strategy described above, you will find yourself creating re-useable navigation logic that you can just paste around as needed.

这篇关于带有登录屏幕的 React-Navigation的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆