React Native 路由/导航篇(还有多少人在用RN…)

菜鸟一枚,入手rn不久,发现react-navigation中文文档都挺旧的,很多无法参考,所以想把我遇到的问题分享出来,帮助后来者

我用的mac mini开发 react-native 0.63 @react-navigation/native 5.9.4 具体可以参考官方文档 不过只有英文版看的磕磕碰碰

react-navigation功能很强大 目前已经更新到6.x
5.9以前的版本很多地方使用都更变了 90%关于react-navigation教程都是5.x前版本

安装

yarn add @react-navigation/native

yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

npx pod-install ios
复制代码

这三个都要安装 我第一次就是没安装全 找了好久原因

React Native 0.60及以上版本是自动链接库的,不需要手动运行react-native link,但如果你用React Native开发的是iOS,还需要手动安装pods来完成库的链接:

npx pod-install ios
复制代码

如果你需要手势库 可以在入口文件index.js或者app.js 顶部添加 import ‘react-native-gesture-handler’

import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';

export default function App() {
  return (
    <NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
  );
}
复制代码

如果你使用了Redux框架,需要把Provider放在最外层,将NavigationContainer包裹在次外层,类似下面这样:

export default class App() {
  return (
    <Provider store={store}>
      <NavigationContainer>
        {/* Screen configuration */}
      </NavigationContainer>
    </Provider>
  );
}
复制代码

导航分类

  1. StackNavigator栈导航 可以理解成头部导航栏
  2. TabNavigator标签导航 可以理解成底部导航栏
  3. DrawerNavigator抽屉导航 可以理解成左右滑出弹窗

因为我只用到StackNavigator 所以主要讲StackNavigator 其他两者使用大同小异

yarn add @react-navigation/stack
复制代码
// In App.js in a new project

import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

function HomeScreen (props) {
  return <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}><Button
    title="Go to Login... again"
    onPress={() => props.navigation.navigate('Login')}
  /></View>
}

function LoginScreen () {
  return <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}><Text>Home</Text></View>
}

const Stack = createStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" >
          {props => <HomeScreen {...props} />}  
        </Stack.Screen>
        <Stack.Screen name="Login" component={LoginScreen} /> 
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;
复制代码

会默认选择第一个Stack.Screen页面为首页

可配置太多了 只会挑重点和常用来讲
以下是他的一些常用参数配置

Stack.Navigator的配置选项

      <Stack.Navigator
           initialRouteName="Home"//初始页面
        screenOptions={{
          title: '测试标题',//初始页面标题
          headerStyle: {
            backgroundColor: 'green'//导航栏背景颜色
          },
          headerTintColor: '#fff',//导航栏字体颜色
          headerTitleStyle: {
            fontWeight: 'bold',
            fontSize: 20
          }
        }}
        keyboardHandlingEnabled={true} //如果为false,则导航到新屏幕时,屏幕键盘不会自动关闭。默认为true
        mode="card"
        /* 
        定义渲染和过渡的样式 
        card:使用标准的iOS和Android屏幕过渡。这是默认值. 
        modal:这有两件事:设置headerMode到screen堆栈,除非指定使屏幕从iOS底部的底部滑入,这是一种常见的iOS模式.
        */
        headerMode="none"
        /* 
        指定标题的呈现方式
        float:渲染停留在顶部的单个标题,并在更改屏幕时进行动画处理。iOS上的常见模式。
        screen:每个屏幕都有一个附加的标题,标题随屏幕一起淡入和淡出。Android上的常见模式。
        none :没有标题。
        */
      >
复制代码

Stack.Screen的配置选项

    <Stack.Screen
          name="Login"
          component={LoginScreen}
          options={{  //配置导航器内的各个屏幕
            title: 'My Login', //名称
            headerShown: true,//是否显示名称
            headerTitle: <View><Text>自定义标题组件</Text></View>,//自定义导航栏名称内容
            headerTitleAlign: "left",//对齐标题。可选择left或center。默认为iOS-center和Android-left
            headerTitleAllowFontScaling: false,//标头标题字体是否应缩放以符合“文本大小”辅助功能设置。默认为false
            headerBackAllowFontScaling: false,//后退按钮标题字体是否应缩放以符合“文本大小”辅助功能设置。默认为false。
            //headerBackImage: <Image source={require('xx.png')}></Image>,//在标题的后退按钮中显示自定义图像
            headerBackTitle: "自定义后退按钮文字",//iOS上的后退按钮使用的标题字符串。默认为上一个场景的headerTitle。
            headerBackTitleVisible: true,//为后退按钮标题是否可见提供了一个合理的默认值,但是如果您想覆盖它,则可以使用true或false在此选项中使用
            headerRight: () => (<View><Text>标题右侧自定义内容</Text></View>),
            //headerLeft: () => (<View><Text>标题左侧自定义内容</Text></View>),
            headerTitleStyle: { backgroundColor: 'orange', color: '#fff' },//标题的样式对象
            // headerTitleStyle
            // headerBackTitleStyle
            // headerLeftContainerStyle
            // headerRightContainerStyle
          }}
        />
复制代码

WeChatd2fc8da293e00f8ef4b317e0824d9d51.png

以上看着很实用但实际中很难满足UI设计 可能我太菜了我项目中顶部导航没用官方的自己写的 因为UI导航样式太多了

跳转动画

还可以配置页面跳转动画 提供的方法太多自行查找

const config = {
  animation: 'spring',
  config: {
    stiffness: 1000,
    damping: 500,
    mass: 3,
    overshootClamping: true,
    restDisplacementThreshold: 0.01,
    restSpeedThreshold: 0.01,
  },
};
<Stack.Screen
  name="Profile"
  component={Profile}
  options={{
    transitionSpec: {
      open: config,
      close: config,
    },
  }}
/>
//forHorizontalIOS 标准的 ios 风格从右侧滑入
//forVerticalIOS 标准的 ios 风格从底部滑入
//forModalPresentationIOS  iOS 13标准 iOS 风格的模态动画
//forFadeFromBottomAndroid 标准的 Android 风格淡入底部的 
//forRevealFromBottomAndroid 标准 Android 风格的 Android 底部展示

import { CardStyleInterpolators } from '@react-navigation/stack';

<Stack.Screen
  name="Profile"
  component={Profile}
  options={{
    title: 'Profile',
    cardStyleInterpolator: CardStyleInterpolators.forFadeFromBottomAndroid,
  }}
/>;
复制代码

导航跳转方式(重点)

function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 30 }}>This is the home screen!</Text>
      <Button
        onPress={() => navigation.navigate('Login')}
        title="Open Modal"
      />
    </View>
  );
}
this.props.navigation.navigate("Login",{id:xxxx})
this.props.navigation.push("Login",{id:xxxx})
this.props.navigation.replace("Login",{id:xxxx})
this.props.navigation.goBack()
this.props.navigation.goBack("Login")
this.props.navigation.pop()
this.props.navigation.pop(n) //n代表返回第几层页面
this.props.navigation.popToTop()

const id=this.props.route.params.id
复制代码
  1. navigate 入栈新页面
  2. push 入栈新页面
  3. replace 替换当前页面(待支付到支付完成页面 后退不需要到待支付就可以用这个)
  4. goBack 后退一页 关闭当前页
  5. pop 默认返回到上一页
  6. popToTop 返回至最顶层页面

navigate、push 、replace区别具体看下面

1449382-20201120170238157-979152608.jpeg

如上图,外部是一个栈容器,此时A页面在最底部,navigate到B页面,为什么此时用navigate没有用push呢,因为在栈内没有B页面时,用navigate和push是一样的,都是进行入栈操作,没有区别,出于习惯使用navigate。下一步,B页面push了一个B页面,此时为何不使用navigate呢,因为栈内若已经存在一个相同页面,navigate就会失去跳转页面的效果,B navigate B代码不会报错,但是也不会进行跳转,因为栈内已经有了B,只有使用push才会进行新的B页面进行入栈操作。现在了解了navigate与push的区别,看上图,进行了一系列入栈操作,形成最终的栈图,下面分三种情况讨论。

1.现在我们处于C页面,若C push A,同样会执行A的继续入栈,但此时若使用C navigate A,则A以上全部页面会执行出栈操作,相当于popToTop方法,回到A页面;

2.现在处于C页面,若C push B,同样会执行B的继续入栈,但此时若使用C navigate B,则会当前的C开始向下寻找B界面,直到找到最近的B界面,进行跳转,C与B中间的页面全部出栈,此时B还可以继续进行返回,一步步返回至A页面;

3.还有一种与图无关的情况,A navigate BrowserPage,此时app接收到一个推送,点击推送进入了新的BrowserPage,若收到推送后跳转页面的逻辑使用的是navigate方法,此时就会出现无法跳转的情况,因为BrowserPage已经在栈内存在(刚刚在浏览网页),所以此处要使用push方法进行入栈操作;

4.A页面navigate B页面,A收到通知要navigate到C界面,此时C入栈并覆盖了B,此时点击返回是回到B界面,因为C执行出栈操作后到了B,而不会直接回到A;

5.replace方法,

replace – replace the current route with a new one
  据官方文档介绍,是用一个新的路由替换掉当前的路由,即使用新的页面替换当前的页面,假设有这样的场景,A navigate到B,B完成任务后要到C,C返回的不是B,而是A,此时使用B replace C即可实现需求。

以上来自www.cnblogs.com/li-wei203/p… 我觉得讲的挺好的就引过来了

监听导航(重点)

这个用的几率挺大的 滑动后退重定向路由 或者 滑动后退离开页面阻拦提示

1.focus – 当屏幕成为焦点时,会发出这个事件

2.blur – 当屏幕失焦时会发出这个事件

3.beforeRemove (version 5.7+ only) – (版本5.7以上)-当用户离开屏幕时, 防止用户离开

4.state (advanced) – (高级)-当导航器的状态改变时发出此事件

5.tabPress (advanced) – 监听tab页切换

const unsubscribe = navigation.addListener('tabPress', e => {
  // Prevent default action
  e.preventDefault();
});

function Profile({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      // do something
    });

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}

或者
<Tab.Screen
  name="Chat"
  component={Chat}
  listeners={({ navigation, route }) => ({
    tabPress: e => {
      // Prevent default action
      e.preventDefault();

      // Do something with the `navigation` object
      navigation.navigate('AnotherPlace');
    },
  })}
/>
复制代码

还有一个方法 阻止屏幕后退设备上的后退按钮事件,可以调用你自己的函数来处理后退行为。此 API 仅能在 Android 上使用。

import React, { Component } from "react";
import { Text, View, StyleSheet, BackHandler, Alert } from "react-native";

class App extends Component {
  backAction = () => {
    Alert.alert("Hold on!", "Are you sure you want to go back?", [
      {
        text: "Cancel",
        onPress: () => null,
        style: "cancel"
      },
      { text: "YES", onPress: () => BackHandler.exitApp() }
    ]);
    return true;
  };

  componentDidMount() {
    BackHandler.addEventListener("hardwareBackPress", this.backAction);
  }

  componentWillUnmount() {
    BackHandler.removeEventListener("hardwareBackPress", this.backAction);
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>Click Back button!</Text>
      </View>
    );
  }
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  },
  text: {
    fontSize: 18,
    fontWeight: "bold"
  }
});

export default App;
});
复制代码

第一次写这么长的文章 我也不知我写了啥 感觉写繁杂了 但是react-navigation的内容真的很多很多。。。感觉rn都没人写了都在推flutter(学不动了) 还有那些人在用啊

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享