1. createAppContainer的作用?
它负责管理您的应用程序状态,并将顶级navigator链接到应用程序环境。 调用createAppContainer后,返回值是个组件,并拥有navigation props。可以像用React组件一样render了。
例如:
import { createAppContainer } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
const RootStack = createStackNavigator({ /* your routes here */ });
const AppContainer = createAppContainer(RootStack);
// Now AppContainer is the main component for React to render
export default AppContainer;
//这样AppContainer就可以当做react组件使用了
<AppContainer
onNavigationStateChange={handleNavigationChange}
uriPrefix="/app"
/>
2. createSwitchNavigator的作用?
SwitchNavigator的目的是卸载所有屏幕,一次只显示一个屏幕。默认情况下,它不处理后退操作,并且在您离开时将导航重置为其默认状态。相当于是把各个路由隔离起来,导航状态互相不受影响。
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
import { createStackNavigator } from 'react-navigation-stack';
// Implementation of HomeScreen, OtherScreen, SignInScreen, AuthLoadingScreen
// goes here.
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createAppContainer(
createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
)
);
上面的例子可以使AuthLoading、App、Auth之间的跳转重置导航状态,就是说互相跳转会重新加载组件,执行组件生命周期事件,以前的路由栈也没了。
使用createSwitchNavigator以后跳转是这样的:
首先跳转到App页面,执行App组件的componentDidMount。
然后由App跳转到Auth,执行App组件的componentWillUnmount,执行Auth组件的componentDidMount。
然后由Auth又跳转到App,执行Auth组件的componentWillUnmount,执行App组件的componentDidMount。
Home和Other之间的跳转不会受createSwitchNavigator的影响,它们之间相互跳转状态还是存在的。
3. 路由切换行为不一致?
是不是有时候组件后退、切换、跳转表现行为不一致?生命周期事件执行不一致?
我总结了下:navigation中有三种行为不同的导航器:栈导航器、标签导航器、Switch导航器。
栈导航器
堆栈导航器主要是createStackNavigator
创建,它的路由是按照栈数据结构存放的。当前屏幕显示的永远是栈顶的screen组件。跳转行为是:栈中不存在就push到栈顶,执行componentDidMount。栈中存在就直接跳转,并且跳转到的组件到栈顶之间的screen组件和导航状态会全部卸载,依次执行componentWillUnMount事件,卸载顺序是冒泡执行,直到栈顶的组件。
createStackNavigator({
A: AScreen,
B: BScreen,
C: CScreen,
});
比如上面的组件,ABC组件间导航状态是由栈组成的,初次到A组件执行A组件的componentDidMount。然后由A跳转到B执行B的componentDidMount,由B跳转到C执行C的componentDidMount,然后C跳转到A,此时栈的顺序是A->B->C,先执行B的componentWillUnMount,然后执行C的componentWillUnMount,最后到A,因为A已经存在所以不会再次执行componentDidMount。
标签导航器
标签导航器由createBottomTabNavigator
/createMaterialBottomTabNavigator
/createMaterialTopTabNavigator
创建。导航状态存放方式和对象一样,不是按照顺序存放的。第一次跳转会执行componentDidMount,以后跳转不会卸载当前组件,不执行生命周期。
createBottomTabNavigator({
A: AScreen,
B: BScreen,
C: CScreen,
});
比如上面的组件,初次到A组件执行A组件的componentDidMount。
由A跳转到B,执行B的componentDidMount。
然后由B跳转到C,执行C的componentDidMount。
然后C跳转到A,不执行生命周期函数。
Switch导航器
Switch导航器是createSwitchNavigator
创建。createSwitchNavigator
的跳转是先卸载当前所有导航,然后加载跳转的导航组件screen。一次只显示一个屏幕。切换就先卸载当前的组件,再加载要切换到的组件。每次只显示一个组件。
createSwitchNavigator({
A: AScreen,
B: BScreen,
C: CScreen,
});
比如上面的组件,初次到A组件执行A组件的componentDidMount。
由A跳转到B,先执行A的componentWillUnMount,然后执行B的componentDidMount。
然后由B跳转到C,先执行A的componentWillUnMount,然后执行C的componentDidMount。
然后C跳转到A,先执行C的componentWillUnMount,然后执行A的componentDidMount。
4. 安卓navigation.goBack() 返回白屏
navigationOptions中gestureEnabled 设为false就好了。或者把这个选项去掉,ios默认是true,android中是false。
也可以这样解决:
function goBack() {
// THIS IS A HACK!!!!!!!
// UPDATE RN NAVIGATION AND DELETE THIS
if(Platform.OS === 'android') {
setTimeout(() => {
navigation.navigate('App');
}, 100);
}else{
navigation.navigate('App');
}
}
参考:
https://github.com/react-navigation/react-navigation/issues/7848
https://github.com/react-navigation/react-navigation/issues?q=gestureEnabled
https://github.com/react-navigation/react-navigation/issues/7938
5. TypeError: Cannot read property ‘default’ of undefined
由于import引入问题导致。
//router.js
import HomePage from './HomePage.js'
export default createStackNavigator({
HomePage: {
screen: HomePage,
}
})
//HomePage.js
import { WorkBench, NoticeList } from './index';//这块引入有问题,不知道为啥。改为下面这种就好了
//import WorkBench from './WorkBench';//这种正常
export default createBottomTabNavigator({
WorkBench: {
screen: WorkBench//报错,不知道啥原因导致,可能版本之间不兼容。可以改为(props) => <WorkBench navigation={props.navigation} />,但会导致问题3嵌套问题
},
NoticeList: {
screen: NoticeList
},
})
//index.js
import WorkBench from './WorkBench';
import NoticeList from './NoticeList';
export {WorkBench,NoticeList}
6. 路由嵌套问题TypeError: No “routes” found in navigation state. Did you try to pass the navigation prop of a React component to a Navigator child?
由于找不到screen属性的React component。
//router.js
import Operation from './operation.js'
export default createStackNavigator({
Operation: {
screen: Operation,
}
})
//operation.js
import OperationCenter from './OperationCenter'
export default createBottomTabNavigator({
OperationCenter: {
screen: (props) => <OperationCenter navigation={props.navigation} />,//错误,由于OperationCenter 不是React组件
//screen: OperationCenter//这样写正确
}
})
//OperationCenter.js
function test(){
return <Text>test</Text>
}
export default createStackNavigator({
Operation: {
screen: test,
}
})
解决方法1:直接引入,不用函数。直接修改operation.js
//operation.js
import OperationCenter from './OperationCenter'
export default createBottomTabNavigator({
OperationCenter: {
screen: OperationCenter//这样写正确
}
})
解决方法2:使用createAppContainer并返回组件。直接修改operation.js
import OperationCenter from './OperationCenter'
import { createAppContainer } from 'react-navigation';
const MainTab = function (props) {
const AppContainer = createAppContainer(OperationCenter);
return <AppContainer />;
};
export default createBottomTabNavigator({
OperationCenter: {
screen: (props) => <MainTab navigation={props.navigation} />,
}
})
7.路由跳转刷新
方法1:使用addListener
https://reactnavigation.org/docs/4.x/navigation-prop#addlistener—subscribe-to-updates-to-navigation-lifecycle
componentDidMount() {
this._navListener = this.props.navigation.addListener('didFocus', () => {
this.getData();
});
}
componentWillUnmount() {
this._navListener.remove();
}
方法2:使用withNavigationFocus
import React, { Component } from 'react';
import { View } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
class TabScreen extends Component {
componentDidUpdate(prevProps) {
if (prevProps.isFocused !== this.props.isFocused) {
// Use the `this.props.isFocused` boolean
// Call any action
}
}
render() {
return <View />;
}
}
// withNavigationFocus returns a component that wraps TabScreen and passes
// in the navigation prop
export default withNavigationFocus(TabScreen);
方法3:使用NavigationEvents
https://reactnavigation.org/docs/4.x/navigation-events
import React from 'react';
import { View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
const MyScreen = () => (
<View>
<NavigationEvents
onWillFocus={payload => console.log('will focus', payload)}
onDidFocus={payload => console.log('did focus', payload)}
onWillBlur={payload => console.log('will blur', payload)}
onDidBlur={payload => console.log('did blur', payload)}
/>
{/*
Your view code
*/}
</View>
);
export default MyScreen;
方法4:导航跳转调用回调函数刷新
用户User页面
const { navigate } = this.props.navigation;
navigate('Login', {
page: 'User',
callBack: () => {
//在此调接口或者改变state会让页面改变
}
});
登录Login页面
const { goBack,state } = this.props.navigation;
// 在登录页面,在goBack之前,将上个页面的方法取到,并回传参数,这样回传的参数会重走render方法
state.params.callback('回调参数');
goBack();
方法5:使用rn的DeviceEventEmitter
DeviceEventEmitter - react-native
//1.js
import { DeviceEventEmitter } from 'react-native';
componentDidMount() {
DeviceEventEmitter.addListener('closeBottomTab', (val) => {
this.setState({ isHidden: val })
});
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('closeBottomTab');
}
//2.js
import { DeviceEventEmitter } from 'react-native';
componentDidMount() {
DeviceEventEmitter.emit('closeBottomTab', false);
}
方法6:底部跳转可以使用导航跳转调用回调函数刷新的解决方法。只需要在tabBarOnPress点击设置中跳转路由传参
tabBarOnPress: async (obj: any) => {
console.log(obj);
try {
const userData = await AsyncStorage.getItem('USER_INFO');
if (userData) {
obj.defaultHandler();
}
else {
obj.navigation.navigate('Login',callback);
}
} catch (e) {
Toast.show(e.message, 'center', 1000);
}
}
如果是Hook可以使用useFocusState hook。
8. 隐藏底部标签栏
正常情况下,都会用createStackNavigator包裹createBottomTabNavigator。
const AppRouter = createStackNavigator({
MyTab: MyTab,
BottomNavigator: BottomNavigator,
});
BottomNavigator.navigationOptions = ({ navigation }) => {
let tabBarVisible = true;
if (navigation.state.index > 0) {
tabBarVisible = false;
}
return {
tabBarVisible,
};
};
const TabNavigator = createBottomTabNavigator({
Feed: FeedScreen,
Profile: ProfileScreen,
});
TabNavigator.navigationOptions = ({ navigation }) => {
const { routeName } = navigation.state.routes[navigation.state.index];
// You can do whatever you like here to pick the title based on the route name
const headerTitle = routeName;
return {
headerTitle,
};
};