React Navigation 6.x 导航器

6,964 阅读10分钟

截屏2021-07-24 下午2.34.57.png

React Navigation 官网:reactnavigation.org/

1. 准备工作

1.1 最低要求

React Navigation 6 要求 react-native 版本至少 0.63.0

1.2 安装依赖

React Native 项目中安装所需的软件包:

yarn add @react-navigation/native@next

还需要安装 react-native-gesture-handlerreact-native-reanimatedreact-native-screensreact-native-safe-area-context

yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context

1.2.1

如果是在 mac 上开发 iOS 项目,需要安装 CocoaPods 来完成项目链接:

npx pod-install ios

1.2.2 react-native-screens

react-native-screens 在 Android 需要修改 android/app/src/main/java/<your package name>/MainActivity.java 文件 。

在 MainActivity class 添加以下代码:

import android.os.Bundle;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(null);
}

截屏2021-07-24 下午3.10.06.png

1.2.3 react-native-gesture-handler

react-native-gesture-handler 在 Android 也需要修改 android/app/src/main/java/<your package name>/MainActivity.java 文件 。

package com.swmansion.gesturehandler.react.example;

import com.facebook.react.ReactActivity;
+ import com.facebook.react.ReactActivityDelegate;
+ import com.facebook.react.ReactRootView;
+ import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;

public class MainActivity extends ReactActivity {

  @Override
  protected String getMainComponentName() {
    return "Example";
  }

+  @Override
+  protected ReactActivityDelegate createReactActivityDelegate() {
+    return new ReactActivityDelegate(this, getMainComponentName()) {
+      @Override
+      protected ReactRootView createRootView() {
+       return new RNGestureHandlerEnabledRootView(MainActivity.this);
+      }
+    };
+  }
}

详情:docs.swmansion.com/react-nativ…

截屏2021-07-24 下午3.18.29.png

完成了 react-native-gesture-handler 的安装,在项目的入口文件(例如 index.jsApp.js )引入 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>
  );
}

1.3 Hello React Navigation

安装堆栈导航库

yarn add @react-navigation/native-stack@next

注意 @react-navigation/native-stack 依赖于 react-native-screens 库

createStackNavigator 函数返回一个包含两个属性的对象: 屏幕导航器。它们都是用于配置导航器的 React 组件。导航器应该将屏幕元素作为子元素来定义路由的配置。

NavigationContainer 是一个管理导航树并包含导航状态的组件。该组件必须包装所有导航器结构。通常,我们会将这个组件呈现在应用程序的根目录下,这个根目录通常是从 app .js 导出的组件。这里我自定义了一个路由文件,然后在app.js 中引入:

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

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

const Stack = createNativeStackNavigator();

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

export default App;

WeChat7f2a724af2bf563cf8ff704f3f7bd404.png

路由的名称的对大小写不敏感——您可以使用小写的 home 或大写的 home。但是一般路由名称用大写。

1.3.1 指定 options

<Stack.Screen
  name="Home"
  component={HomeScreen}
  options={{ title: 'Overview' }}
/>

1.3.2 传递 props

<Stack.Screen name="Home">
    {props => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>

React Navigation 使用指南

Tab navigation

安装依赖

yarn add @react-navigation/bottom-tabs@next

示例

import * as React from 'react';
import { TextView } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import IconFont from '../src/iconfont';


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

function SettingsScreen() {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Settings!</Text>
    </View>
  );
}

const Tab = createBottomTabNavigator();

function MyTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Home" component={HomeScreen} />
      <Tab.Screen name="Settings" component={SettingsScreen} />
    </Tab.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            console.log('color: ', color);
            if (route.name === 'Home') {
              return <IconFont name="iconshouye" size={size} color={color} />;
            } else if (route.name === 'Settings') {
              return <IconFont name="iconshezhi1" size={size} color={color} />;
            }
          },
          tabBarActiveTintColor: 'tomato',
          tabBarInactiveTintColor: 'gray',
        })}>
        <Tab.Screen name="Home" component={HomeScreen} options={{ tabBarBadge: 3 }} />
        <Tab.Screen name="Settings" component={SettingsScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

屏幕录制2021-08-07 下午8 (2).gif

  1. tabBarActiveTintColor 表示 TabBarIcon 选中颜色,tabBarInactiveTintColor 表示未选中颜色。

  2. tabBarBadge

<Tab.Screen
  name="Home"
  component={HomeScreen}
  options={{ tabBarBadge: 3 }}
/>

Drawer navigation

安装依赖

yarn add @react-navigation/drawer@next

示例

import 'react-native-gesture-handler';
import * as React from 'react';
import { View, Button, Text, Image, Alert } from 'react-native';
import { NavigationContainer, DrawerActions } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import {
  createDrawerNavigator,
  DrawerContentScrollView,
  DrawerItemList,
  DrawerItem,
} from '@react-navigation/drawer';

function Feed({ navigation }) {
  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>Feed Screen</Text>
      <Button title="Open drawer" onPress={() => navigation.openDrawer()} />
      <Button
        title="Toggle drawer"
        onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
      />
    </View>
  );
}

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

function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      <DrawerItem
        label="Close drawer"
        onPress={() => props.navigation.closeDrawer()}
      />
      <DrawerItem
        label="Toggle drawer"
        onPress={() => props.navigation.toggleDrawer()}
      />
    </DrawerContentScrollView>
  );
}

const Drawer = createDrawerNavigator();

function MyDrawer() {
  return (
    <Drawer.Navigator
      drawerContent={props => <CustomDrawerContent {...props} />}>
      <Drawer.Screen name="Feed" component={Feed} />
      <Drawer.Screen name="Notifications" component={Notifications} />
    </Drawer.Navigator>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <MyDrawer />
    </NavigationContainer>
  );
}

屏幕录制2021-08-08 下午12.59.36.gif

切换 Drawer 的方式:

  1. navigation.openDrawer()navigation.closeDrawer();

  2. navigation.toggleDrawer();

  3. navigation.dispatch(DrawerActions.openDrawer())、navigation.dispatch(DrawerActions.closeDrawer())、navigation.dispatch(DrawerActions.toggleDrawer());

刘海屏

React Native 处理刘海屏问题需要用到 react-native-safe-area-context 这个库。

00-intro-4709ed78711b21c7bd54d2a1385262bb.png

  • 当渲染没有 header 导航或者 tabBar 导航时
import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function Demo() {
  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'space-between',
        alignItems: 'center',
      }}>
      <Text>This is top text.</Text>
      <Text>This is bottom text.</Text>
    </View>
  );
}
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ headerShown: false }}>
        <Stack.Screen name="Home">
          {() => (
            <Tab.Navigator
              initialRouteName="Analitics"
              tabBar={() => null}
              screenOptions={{ headerShown: false }}>
              <Tab.Screen name="Analitics" component={Demo} />
              <Tab.Screen name="Profile" component={Demo} />
            </Tab.Navigator>
          )}
        </Stack.Screen>

        <Stack.Screen name="Settings" component={Demo} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

WeChat0db98f17744982c860343473223144ba.png

  • 使用 react-native-safe-area-context
import * as React from 'react';
import { Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';

function Demo() {
  return (
    <SafeAreaView
      style={{
        flex: 1,
        justifyContent: 'space-between',
        alignItems: 'center',
      }}>
      <Text>This is top text.</Text>
      <Text>This is bottom text.</Text>
    </SafeAreaView>
  );
}

const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator
          initialRouteName="Home"
          screenOptions={{ headerShown: false }}>
          <Stack.Screen name="Home">
            {() => (
              <Tab.Navigator
                initialRouteName="Analitics"
                tabBar={() => null}
                screenOptions={{ headerShown: false }}>
                <Tab.Screen name="Analitics" component={Demo} />
                <Tab.Screen name="Profile" component={Demo} />
              </Tab.Navigator>
            )}
          </Stack.Screen>
          <Stack.Screen name="Settings" component={Demo} />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

WeChat73959c1d10948aae3e7354a97c52924e.png

使用 hook 方式

import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator } from '@react-navigation/stack';
import {
  SafeAreaProvider,
  useSafeAreaInsets,
} from 'react-native-safe-area-context';

function Demo() {
  const insets = useSafeAreaInsets();
  return (
    <View
      style={{
        paddingTop: insets.top,
        // paddingBottom: insets.bottom,

        flex: 1,
        justifyContent: 'space-between',
        alignItems: 'center',
      }}>
      <Text>This is top text.</Text>
      <Text>This is bottom text.</Text>
    </View>
  );
}

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator
          initialRouteName="Home"
          screenOptions={{ headerShown: false }}>
          <Stack.Screen name="Home">
            {() => (
              <Tab.Navigator
                initialRouteName="Analitics"
                tabBar={() => null}
                screenOptions={{ headerShown: false }}>
                <Tab.Screen name="Analitics" component={Demo} />
                <Tab.Screen name="Profile" component={Demo} />
              </Tab.Navigator>
            )}
          </Stack.Screen>

          <Stack.Screen name="Settings" component={Demo} />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

WeChataf624c7b94e3c2f0df1c96f7fcbbe8b7.png

重点代码:

  • const insets = useSafeAreaInsets()
  • style={{paddingTop: insets.top,... }}

基于路由的不同状态栏配置

如果您没有导航标题,或者您的导航标题根据路线更改颜色,您需要确保为内容使用正确的颜色。

堆栈导航

import * as React from 'react';
import { Text, StatusBar, Button, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';

function Screen1({ navigation }) {
  return (
    <SafeAreaView style={[styles.container, { backgroundColor: '#6a51ae' }]}>
      <StatusBar barStyle="light-content" backgroundColor="#6a51ae" />
      <Text style={{ color: '#fff' }}>Light Screen</Text>
      <Button
        title="Next screen"
        onPress={() => navigation.navigate('Screen2')}
      />
    </SafeAreaView>
  );
}

function Screen2({ navigation }) {
  return (
    <SafeAreaView style={[styles.container, { backgroundColor: '#ecf0f1' }]}>
      <StatusBar barStyle="dark-content" backgroundColor="#ecf0f1" />
      <Text>Dark Screen</Text>
      <Button
        title="Next screen"
        onPress={() => navigation.navigate('Screen1')}
      />
    </SafeAreaView>
  );
}

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Stack.Navigator screenOptions={{ headerShown: false }}>
          <Stack.Screen name="Screen1" component={Screen1} />
          <Stack.Screen name="Screen2" component={Screen2} />
        </Stack.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

statusbar-stack-demo-695c18cfc1489cd5c4c18827bc66befb.gif

标签导航和抽屉导航

如果您使用选项卡或抽屉导航器,则它会更复杂一些,StatusBar 配置可能会被覆盖。

为了解决这个问题,我们必须让状态栏组件知道屏幕焦点并仅在屏幕聚焦时才渲染它。 我们可以通过使用 useIsFocused 钩子并创建一个包装组件来实现这一点。

import * as React from 'react';
import { StatusBar } from 'react-native';
import { useIsFocused } from '@react-navigation/native';

function FocusAwareStatusBar(props) {
  const isFocused = useIsFocused();

  return isFocused ? <StatusBar {...props} /> : null;
}

此时屏幕 Screen1.jsScreen2.js 将使用 FocusAwareStatusBar 组件而不是 React Native 的 StatusBar 组件。

function Screen1({ navigation }) {
  return (
    <SafeAreaView style={[styles.container, { backgroundColor: '#6a51ae' }]}>
      <FocusAwareStatusBar barStyle="light-content" backgroundColor="#6a51ae" />
      <Text style={{ color: '#fff' }}>Light Screen</Text>
      <Button
        title="Next screen"
        onPress={() => navigation.navigate('Screen2')}
        color="#fff"
      />
    </SafeAreaView>
  );
}

function Screen2({ navigation }) {
  return (
    <SafeAreaView style={[styles.container, { backgroundColor: '#ecf0f1' }]}>
      <FocusAwareStatusBar barStyle="dark-content" backgroundColor="#ecf0f1" />
      <Text>Dark Screen</Text>
      <Button
        title="Next screen"
        onPress={() => navigation.navigate('Screen1')}
      />
    </SafeAreaView>
  );
}

statusbar-drawer-demo-3f6ce5c56a2086d8aba8ab2f1f3f513e.gif

statusbar-tab-demo-c5f932fea901d54665ed07f921ad8322.gif

监听屏幕聚焦

navigation.addListener

import * as React from 'react';
import { View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function ProfileScreen({ navigation }) {
  React.useEffect(() => {
    const unsubscribe = navigation.addListener('focus', () => {
      alert('Screen is focused');
    });
    // Return the function to unsubscribe from the event so it gets removed on unmount
    return unsubscribe;
  }, []);

  return <View />;
}

function HomeScreen() {
  return <View />;
}

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Profile" component={ProfileScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

屏幕录制2021-08-15 下午1.54.53.gif

useFocusEffect

import * as React from 'react';
import { View } from 'react-native';
import { NavigationContainer, useFocusEffect } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';

function ProfileScreen() {
  useFocusEffect(
    React.useCallback(() => {
      alert('Screen was focused');
      // Do something when the screen is focused
      return () => {
        alert('Screen was unfocused');
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, []),
  );

  return <View />;
}

function HomeScreen() {
  return <View />;
}

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Profile" component={ProfileScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

屏幕录制2021-08-15 下午1.59.51.gif

useIsFocused

import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer, useIsFocused } from '@react-navigation/native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';

function ProfileScreen() {
  // This hook returns `true` if the screen is focused, `false` otherwise
  const isFocused = useIsFocused();

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>{isFocused ? 'focused' : 'unfocused'}</Text>
    </View>
  );
}

function HomeScreen() {
  return <View />;
}

const Tab = createMaterialTopTabNavigator();

export default function App() {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <Tab.Navigator tabBarPosition="top" style={{ marginTop: 24 }}>
          <Tab.Screen name="Home" component={HomeScreen} />
          <Tab.Screen name="Profile" component={ProfileScreen} />
        </Tab.Navigator>
      </NavigationContainer>
    </SafeAreaProvider>
  );
}

屏幕录制2021-08-15 下午2.25.58.gif

任意组件访问导航

useNavigation

import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '@react-navigation/native';

function GoToButton({ screenName }) {
  const navigation = useNavigation();

  return (
    <Button
      title={`Go to ${screenName}`}
      onPress={() => navigation.navigate(screenName)}
    />
  );
}

在没有 navigation prop 的情况下导航跳转

有时需要从无法访问 navigation prop 的地方触发导航操作,例如 Redux 中间件。 对于这种情况,可以从导航容器调度导航操作。

如果从组件内部导航无需向下传递导航的方法,可以用 useNavigation。如果导航需要向下传递的话,可以通过 ref 访问根导航对象并将其传递给我们稍后将用于导航的 RootNavigation

// App.js

import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './RootNavigation';

export default function App() {
  return (
    <NavigationContainer ref={navigationRef}>{/* ... */}</NavigationContainer>
  );
}
// RootNavigation.js

import { createNavigationContainerRef } from '@react-navigation/native';

export const navigationRef = createNavigationContainerRef()

export function navigate(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.navigate(name, params);
  }
}

还可以添加其他导航操作:

import { StackActions } from '@react-navigation/native';

// ...

export function push(...args) {
  if (navigationRef.isReady()) {
    navigationRef.dispatch(StackActions.push(...args));
  }
}

举一个例子

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

const navigationRef = createNavigationContainerRef();

function navigate(name, params) {
  if (navigationRef.isReady()) {
    navigationRef.navigate(name, params);
  }
}

function Home() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button
        title="Go to Settings"
        onPress={() => navigate('Settings', { userName: 'Lucy' })}
      />
    </View>
  );
}

function Settings({ route }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Hello {route.params.userName}</Text>
      <Button title="Go to Home" onPress={() => navigate('Home')} />
    </View>
  );
}

const RootStack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer ref={navigationRef}>
      <RootStack.Navigator>
        <RootStack.Screen name="Home" component={Home} />
        <RootStack.Screen name="Settings" component={Settings} />
      </RootStack.Navigator>
    </NavigationContainer>
  );
}

屏幕录制2021-08-29 下午2.23.21.gif

React Navigation TypeScript

navigator

要对我们的路由名称参数进行类型检查,我们需要做的第一件事是创建一个对象类型,其中包含路由名称到路由参数的映射。 例如,假设我们在根导航器中有一个名为 Profile 的路由,它应该有一个参数 userId:

type RootStackParamList = {
  Profile: { userId: string };
};

同样,我们需要对每条路线做同样的事情:

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};

指定 undefined 意味着路由没有参数。

定义映射后,我们需要告诉导航器使用它。 为此,我们可以将其作为泛型传递给 createXNavigator 函数:

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

const RootStack = createStackNavigator<RootStackParamList>();

使用

<RootStack.Navigator initialRouteName="Home">
  <RootStack.Screen name="Home" component={Home} />
  <RootStack.Screen
    name="Profile"
    component={Profile}
    initialParams={{ userId: user.id }}
  />
  <RootStack.Screen name="Feed" component={Feed} />
</RootStack.Navigator>

screens

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

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};

type Props = NativeStackScreenProps<RootStackParamList, 'Profile'>;

NativeStackScreenProps 需要 2 个泛型,我们之前定义的 param 列表对象,以及当前路由的名称

同样的,可以导入 StackScreenProps for @react-navigation/stackDrawerScreenProps from @react-navigation/drawerBottomTabScreenProps from @react-navigation/bottom-tabs 等等。

对于 function components:

function ProfileScreen({ route, navigation }: Props) {
  // ...
}

对于 class components:

class ProfileScreen extends React.Component<Props> {
  render() {
    // ...
  }
}

可以从 Props 类型中获取 navigationroute 的类型:

type ProfileScreenNavigationProp = Props['navigation'];

type ProfileScreenRouteProp = Props['route'];

或者也可以分别定义 navigationroute 的 props 。

  • 对于 navigation props 来说, 要获取 navigation 的类型,我们需要从导航器中导入相应的类型。
import { NativeStackNavigationProp } from '@react-navigation/native-stack';

type ProfileScreenNavigationProp = NativeStackNavigationProp<
  RootStackParamList,
  'Profile'
>;

同样的,你也可以导入 import { StackNavigationProp } from '@react-navigation/stack'import { DrawerNavigationProp } from '@react-navigation/drawer'import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs'

  • 对于 route prop ,我们需要使用 @react-navigation/native 中的 RouteProp 类型。
import { RouteProp } from '@react-navigation/native';

type ProfileScreenRouteProp = RouteProp<RootStackParamList, 'Profile'>;

嵌套导航

在嵌套导航器中检查 screens 和 params
function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Messages" component={Messages} />
    </Tab.Navigator>
  );
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={Home}
          options={{ headerShown: false }}
        />
        <Stack.Screen name="Profile" component={Profile} />
        <Stack.Screen name="Settings" component={Settings} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

在上面的嵌套导航中,可以通过传递嵌套屏幕的 screen 和 params 属性来导航到嵌套导航中的屏幕。

navigation.navigate('Home', {
  screen: 'Feed',
  params: { sort: 'latest' },
});

为了能够进行类型检查,我们需要从包含嵌套导航的屏幕中提取参数。 这可以使用 NavigatorScreenParams

import { NavigatorScreenParams } from '@react-navigation/native';

type TabParamList = {
  Home: NavigatorScreenParams<StackParamList>;
  Profile: { userId: string };
};

组合导航

嵌套导航器时,屏幕的导航道具是多个导航道具的组合。 例如,如果我们在堆栈中有一个选项卡,则导航属性将同时具有 jumpTo(来自选项卡导航器)和 push(来自堆栈导航器)。 为了更容易地组合来自多个导航器的类型,您可以使用 CompositeScreenProps 类型。

import { CompositeScreenProps } from '@react-navigation/native';
import { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import { StackScreenProps } from '@react-navigation/stack';

type ProfileScreenNavigationProp = CompositeScreenProps<
  BottomTabScreenProps<TabParamList, 'Profile'>,
  StackScreenProps<StackParamList>
>;

对于多个父导航器,应嵌套此辅助类型:

type ProfileScreenNavigationProp = CompositeScreenProps<
  BottomTabScreenProps<TabParamList, 'Profile'>,
  CompositeScreenProps<
    StackScreenProps<StackParamList>,
    DrawerScreenProps<DrawerParamList>
  >
>;

如果单独注释 navigation ,则可以改用 CompositeNavigationProp。 用法类似于 CompositeScreenProps:

import { CompositeNavigationProp } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { StackNavigationProp } from '@react-navigation/stack';

type ProfileScreenNavigationProp = CompositeNavigationProp<
  BottomTabNavigationProp<TabParamList, 'Profile'>,
  StackNavigationProp<StackParamList>
>;

声明 useNavigation

const navigation = useNavigation<ProfileScreenNavigationProp>();

声明 useRoute

const route = useRoute<ProfileScreenRouteProp>();

声明 options、screenOptions

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

const options: StackNavigationOptions = {
  headerShown: false,
};

同样的,你也可以导入 import { DrawerNavigationOptions } from '@react-navigation/drawer'、import { BottomTabNavigationOptions } from '@react-navigation/bottom-tabs'等等。

声明 ref、NavigationContainer

createNavigationContainerRef

如果使用 createNavigationContainerRef() 方法创建 ref,则可以通过以下方式进行类型检查:

import { createNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = createNavigationContainerRef<RootStackParamList>();
useNavigationContainerRef
import { createNavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = useNavigationContainerRef<RootStackParamList>();
useRef
import { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = React.useRef<NavigationContainerRef<RootStackParamList>>(null);
createRef
import { NavigationContainerRef } from '@react-navigation/native';

// ...

const navigationRef = React.createRef<NavigationContainerRef<RootStackParamList>>();

React Navigation 几个重要 API

Group

Group 组件用于在导航器内对多个屏幕进行分组。

const Stack = createStackNavigator(); // Stack contains Screen & Navigator properties

<Stack.Navigator>
  <Stack.Group
    screenOptions={{ headerStyle: { backgroundColor: 'papayawhip' } }}
  >
    <Stack.Screen name="Home" component={HomeScreen} />
    <Stack.Screen name="Profile" component={ProfileScreen} />
  </Stack.Group>
  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="Search" component={SearchScreen} />
    <Stack.Screen name="Share" component={ShareScreen} />
  </Stack.Group>
</Stack.Navigator>

Props

screenOptions
<Stack.Group
  screenOptions={{
    presentation: 'modal',
  }}
>
  {/* screens */}
</Stack.Group>
<Stack.Group
  screenOptions={({ route, navigation }) => ({
    title: route.params.title,
  })}
>
  {/* screens */}
</Stack.Group>

Screen

const Stack = createNativeStackNavigator(); // Stack contains Screen & Navigator properties

<Stack.Navigator>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>

Props

name
<Stack.Screen name="Profile" component={ProfileScreen} />

name 用于跳转到 Screen

navigation.navigate('Profile');
options

options 用于配置屏幕在导航器中的显示方式的选项, 它接受一个对象或一个返回一个对象的函数。

  • 接受一个对象
<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  options={{
    title: 'Awesome app',
  }}
/>
  • 接受一个返回一个对象的函数
<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  options={({ route, navigation }) => ({
    title: route.params.userId,
  })}
/>
  • initialParams
<Stack.Screen
  name="Details"
  component={DetailsScreen}
  initialParams={{ itemId: 42 }}
/>
  • getId
<Stack.Screen
  name="Profile"
  component={ProfileScreen}
  getId={({ params }) => params.userId}
/>
  • component
<Stack.Screen name="Profile" component={ProfileScreen} />
  • getComponent
<Stack.Screen
  name="Profile"
  getComponent={() => require('./ProfileScreen').default}
/>
  • children
<Stack.Screen name="Profile">
  {(props) => <ProfileScreen {...props} />}
</Stack.Screen>

Route prop

key

屏幕的唯一键, 导航到此屏幕时自动创建或添加。

name

屏幕名称。

path

当通过 deep link 打开屏幕时,打开屏幕的路径的可选字符串。

params

包含在导航时定义的参数的可选对象,例如 navigate('Twitter', { user: 'Dan Abramov' })

function ProfileScreen({ route }) {
  return (
    <View>
      <Text>This is the profile screen of the app</Text>
      <Text>{route.name}</Text>
    </View>
  );
}

参考资料

react-navigation