Hen*_*Lim 61 react-native react-navigation
我正在尝试使用react-navigation来创建一个没有tabbar和header的初始LOGIN屏幕,一旦用户成功通过身份验证,就会导航到另一个名为LISTRECORD的屏幕,该屏幕有一个tabbar,header和no back按钮选项.任何人都有这方面的经验,可以分享?
总之,我试图通过反应导航实现的目标如下所述......
屏幕1:登录屏幕(无标题和标签栏)经过
身份验证...
屏幕2:LISTRECORD(标题,标签栏和无后退按钮)
标签栏还包含其他标签,用于导航到屏幕3,屏幕4 ...
agm*_*984 49
2017年10月 我发现这个令人难以置信的混乱,所以这是我从上到下的解决方案:
我建议开始一个新项目,然后将所有这些粘贴在其中并在之后进行研究.我对代码进行了大量评论,所以如果您遇到任何特定领域,也许上下文可以帮助您重回正轨.
这篇文章展示了如何:
- 完全设置React Native以运行react-navigation
- 正确地与Redux集成
- 处理Android后退按钮
- Nest Stack Navigators
- 从子导航器导航到父导航器
- 重置导航堆栈
- 在从子项导航到父项(嵌套)时重置导航堆栈
index.js
import { AppRegistry } from 'react-native'
import App from './src/App'
AppRegistry.registerComponent('yourappname', () => App)
Run Code Online (Sandbox Code Playgroud)
src/App.js(这是最重要的文件,因为它将所有碎片整合在一起)
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
Run Code Online (Sandbox Code Playgroud)
SRC /导航/ 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
}
Run Code Online (Sandbox Code Playgroud)
SRC /存储/ 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
Run Code Online (Sandbox Code Playgroud)
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
})
Run Code Online (Sandbox Code Playgroud)
SRC /组件/认证/ SignUpForm.js
我会在这里给你看一个例子.这不是我的,我只是在这个摇摇晃晃的StackOverflow编辑器中输入它.如果你欣赏它,请给我竖起大拇指:)
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
Run Code Online (Sandbox Code Playgroud)
SRC /组件/认证/ LoginForm.js
我还会用超级涂料按钮向你展示一种愚蠢的款式
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
Run Code Online (Sandbox Code Playgroud)
SRC /组件/认证/ Splash.js
这是一个你可以玩的闪屏.我正在使用它像一个高阶组件:
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)
Run Code Online (Sandbox Code Playgroud)
SRC /组件/认证/ login_actions.js
我将向您展示initializeApp(),以便您获得一些想法:
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
}
}
Run Code Online (Sandbox Code Playgroud)
在其他用例中,您可能更喜欢高阶组件.它们的工作方式与React for web完全相同.斯蒂芬格里德关于Udemy的教程是最好的时期.
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)
}
Run Code Online (Sandbox Code Playgroud)
你就像这样使用它:
import requireAuth from '../HOC/require_auth'
class RestrictedArea extends Component {
// ... normal view component
}
//map state to props
export default connect(mapStateToProps, actions)(requireAuth(RestrictedArea))
Run Code Online (Sandbox Code Playgroud)
在那里,这就是我希望有人告诉并向我展示的一切.
TLDR的
App.js,而nav_reducer.js文件是绝对最重要的是得到正确的.其余的都很熟悉.我的例子可以让你加速进入一个野蛮的生产力机器.
[编辑]这是我的退出动作创建者.如果您希望擦除导航堆栈以便用户无法按下Android硬件后退按钮并返回需要身份验证的屏幕,您会发现它非常有用:
//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,
}
}
Run Code Online (Sandbox Code Playgroud)
[奖励编辑]如何从子堆栈导航器导航到父堆栈导航器?
如果要从其中一个子Stack Navigators导航并重置堆栈,请执行以下操作:
this.props.navigation可以在其中使用<Something /><Something navigation={this.props.navigation} />this.props.navigation在此子组件中的可用性this.props.navigation.navigate('OtherStackScreen'),你应该看到React Native神奇地去那里没有问题但是,我希望在导航到父堆栈时重置整个堆栈.
this.props.handleSubmit(data, this.props.navigation)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' })]
}))
}
}
Run Code Online (Sandbox Code Playgroud)
我在企业级React Native应用程序中使用此代码,它工作得很漂亮.
react-navigation就像函数式编程一样.它被设计成以小的"纯导航"片段处理,这些片段组合在一起.如果您采用上述策略,您将发现自己创建可重复使用的导航逻辑,您可以根据需要粘贴它.
par*_*ker 30
虽然曼杰特认为会起作用,但它并不是一个好的导航结构.
你应该做的是退后一步,处理另一个层面的所有事情.
顶级导航器应该是呈现登录屏幕的堆栈导航器.这个最顶级导航器中的另一个屏幕应该是您的应用程序的主导航器.满足登录状态后,将主堆栈重置为Main-Navigator.
这种结构的原因是:
A-如果您需要在登录未来之前添加入职信息怎么办?
B-如果您需要在主导航环境之外导航(例如:您的主导航是标签而您想要非标签视图),该怎么办?
如果您的最顶层导航器是一个显示登录屏幕和其他导航器的Stack-Navigator,那么您的应用程序的导航结构可以正确缩放.
我不相信如上所述,登录屏幕或堆栈导航器的条件渲染是一个好主意....相信我......我走了那条路.
Man*_*ngh 12
这就是我实现这一功能的方式.
文件0)index.android.js
'use strict'
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import Root from 'src/containers/Root'
AppRegistry.registerComponent('Riduk', () => Root);
Run Code Online (Sandbox Code Playgroud)
文件1)我的Root.js
class Root extends Component {
constructor(props) {
super(props);
this.state = {
authenticated:false,
isLoading:true,
store: configureStore(() => this.setState({isLoading: false})),
};
}
componentDidMount() {
//you can do check with authentication with fb, gmail and other right here
/* firebase.auth().onAuthStateChanged((user) => {
if (user) {
api.resetRouteStack(dispatch, "UserProfile");
console.log("authenticated", user);
} else {
api.resetRouteStack(dispatch, "Landing");
console.log("authenticated", false);
}
});*/
}
render() {
if (this.state.isLoading) { //checking if the app fully loaded or not, splash screen can be rendered here
return null;
}
return (
<Provider store={this.state.store}>
<App/>
</Provider>
);
}
}
module.exports = Root;
Run Code Online (Sandbox Code Playgroud)
2)App.js
import AppWithNavigationState,{AppBeforeLogin} from './AppNavigator';
class App extends Component{
constructor(props){
super(props);
}
render(){
let {authenticated} = this.props;
if(authenticated){
return <AppWithNavigationState/>;
}
return <AppBeforeLogin/>
}
}
export default connect(state =>({authenticated: state.user.authenticated}))(App);
Run Code Online (Sandbox Code Playgroud)
3)AppNavigator.js
'use strict';
import React, {Component} from 'react';
import { View, BackAndroid, StatusBar,} from 'react-native';
import {
NavigationActions,
addNavigationHelpers,
StackNavigator,
} from 'react-navigation';
import { connect} from 'react-redux';
import LandingScreen from 'src/screens/landingScreen';
import Login from 'src/screens/login'
import SignUp from 'src/screens/signUp'
import ForgotPassword from 'src/screens/forgotPassword'
import UserProfile from 'src/screens/userProfile'
import Drawer from 'src/screens/drawer'
const routesConfig = {
//Splash:{screen:SplashScreen},
Landing:{screen:LandingScreen},
Login: { screen: Login },
SignUp: { screen: SignUp },
ForgotPassword: { screen: ForgotPassword },
UserProfile:{screen:UserProfile},
};
export const AppNavigator = StackNavigator(routesConfig, {initialRouteName:'UserProfile'}); //navigator that will be used after login
Run Code Online (Sandbox Code Playgroud)
export const AppBeforeLogin = StackNavigator(routesConfig); //登录前的naviagtor
class AppWithNavigationState extends Component{
constructor(props) {
super(props);
this.handleBackButton = this.handleBackButton.bind(this);
}
componentDidMount() {
BackAndroid.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackAndroid.removeEventListener('hardwareBackPress', this.handleBackButton);
}
//added to handle back button functionality on android
handleBackButton() {
const {nav, dispatch} = this.props;
if (nav && nav.routes && nav.routes.length > 1) {
dispatch(NavigationActions.back());
return true;
}
return false;
}
render() {
let {dispatch, nav} = this.props;
return (
<View style={styles.container}>
{(api.isAndroid()) &&
<StatusBar
backgroundColor="#C2185B"
barStyle="light-content"
/>
}
<AppNavigator navigation={addNavigationHelpers({ dispatch, state: nav })}/>
</View>
);
}
};
export default connect(state =>({nav: state.nav}))(AppWithNavigationState);
//module.exports = AppWithNavigationState;
Run Code Online (Sandbox Code Playgroud)
这是我基于@parker建议的解决方案:
这段代码完全可以完成上述任务.
创建一个新的react-native项目,然后将下面的代码复制到index.ios.js和/或index.android.js中以查看它是否正常工作.
import React, { Component } from 'react';
import {
AppRegistry,
Text,
Button
} from 'react-native';
import { StackNavigator, NavigationActions } from 'react-navigation';
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Main' })
]
});
class LoginScreen extends Component {
login() {
this.props.navigation.dispatch(resetAction);
}
render() {
return <Button title='Login' onPress={() => {this.login()}} />;
}
}
class FeedScreen extends Component {
render() {
return <Text>This is my main app screen after login</Text>;
}
}
//Create the navigation
const MainNav = StackNavigator({
Feed: { screen: FeedScreen },
});
const TopLevelNav = StackNavigator({
Login: { screen: LoginScreen },
Main: { screen: MainNav },
}, {
headerMode: 'none',
});
AppRegistry.registerComponent('ReactNav2', () => TopLevelNav);
Run Code Online (Sandbox Code Playgroud)
使选项卡栏和标题分离组件,并且仅将它们包含在其他组件中。关于禁用“BACK”,文档中有一个关于“阻止导航操作”的部分:https ://reactnavigation.org/docs/routers/
您应该能够将其用于屏幕 2。
| 归档时间: |
|
| 查看次数: |
44627 次 |
| 最近记录: |