react-navigation4.x导航跳转理解和问题总结

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

使用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,
  };
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值