react navigation 文档学习

957 阅读6分钟

使用 react navigation 库的时候,其中的导航器都有自己独立的库,用到的时候要自行安装。

使用 NavigationContainer 包裹项目

NavigationContainer 为一个组件,通常我们会用它来包裹我们的根组件。

NavigationContainer 可以设置一个 ref 属性,这个 ref 可以使用 useNavigationContainerRef() 方法创建也可以是普通的 React.createRef()。ref 对象中包含所有的导航操作,比如 navigategoBack等。

堆栈导航器 Stack Navigator

npm install @react-navigation/native-stack

在浏览器中会记录路由的历史,然后可以后退前进,这在 app 上是不提供的,所以为了实现这种效果,提供了 createNativeStackNavigator

使用 createNativeStackNavigator 创建堆栈导航器的时候,会返回一个对象,这个对象包含2个属性:

  1. navigator : 组件,应该包含 screen 组件。
  2. screen : 组件,有几个 screen 组件表示导航器中有几个路由。
const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home" screenOptions={{animation: 'slide_from_left'}}>
        <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Overview' }}/>
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

补充属性:

  1. Navigator 上的 initialRouteName 属性可以设置初始路由。
  2. Screen 上的必填属性为 name 和 component,指定路由名称和显示的组件。
  3. Screen 上的 options 属性可以覆盖路由的默认行为和样式。
  4. Navigator 上的 screenOptions 可以覆盖其下所有 Screen 默认行为和样式。

路由转跳

每一个 screen 组件都会接收一个 navigation 参数,navigation 提供了四种转跳页面的方式:

  1. navigate('RouteName') : 如果转跳的页面在导航历史中,则会直接转跳到以前的记录。如果转跳的是一个新页面,则会在导航历史中添加一条记录。
  2. push('RouteName') : 不管导航历史,始终转跳一个新页面,在导航历史中添加一条记录。
  3. goBack() : 回到上一条导航历史,安卓上的硬件返回键也符合这一条。
  4. popToTop() : 回到第一条导航记录。
function HomeScreen({ navigation }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details')}
      />
    </View>
  );
}

传递参数

每一个 screen 组件都会接收一个 route 参数,routeparams 参数可以接收路由参数。

function DetailsScreen({ route, navigation }) {
  const { itemId, otherParam } = route.params;
  return ( ... );
}

我们向路由中设置参数有2种方式:

  1. navigatepush接受第二个可选参数,以便将参数传递到导航到的路由
navigation.navigate('Details', {
            itemId: 86,
            otherParam: 'anything you want here',
          });
  1. screen 组件上定义
<Stack.Screen
  name="Details"
  component={DetailsScreen}
  initialParams={{ itemId: 42 }}
/>

我们还可以通过 navigation.setParams({ ... }); 改变参数

设置导航头部

  1. 设置头部的 title
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Home' }}/>

但是,当我们希望头部标题根据参数显示的时候,我们可以在 options 中设置一个方法,这个方法的参数是包含 navigationroute 对象。

<Stack.Screen name="Home" component={HomeScreen} 
     options={({ route }) => ({ title: route.params.name })/>

我们也可以使用 navigation.setOptions({ ... }) 改变 options 中的属性。

  1. 使用自定义组件定义 title
function LogoTitle() {
  return (
    <Image style={{ width: 50, height: 50 }} source={require('@expo/snack-static/react-native-logo.png')} />
  );
}
function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen}
        options={{ headerTitle: (props) => <LogoTitle {...props} /> }} />
    </Stack.Navigator>
  );
}

使用图片代替头部标题,注意这里使用的是 headerTitle 属性而不是 title 属性。当属性值是一个方法的时候,提供一个参数对象,其中包含 navigation 和 route 属性。

  1. 设置头部的样式: headerStyle / headerTintColor / headerTitleStyle

  2. 设置头部按钮 我们可以在 Stack.ScreenheaderRightheaderLeft 上直接定义按钮组件,实现在头部添加交互功能的按钮,但是这里添加的按钮,我们无法指向我们这个 screen 中的方法。所以我们更常用的是,在我们的 screen 组件中,使用 navigation.setOptions({ ... }) 去添加或修改 headerRightheaderLeft

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);
  React.useEffect(() => {badge
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount((c) => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);
  return <Text>Count: {count}</Text>;
}

我们有默认的后退按钮,当堆栈历史中存在可回退的页面时显示,我们可以使用 headerLeft 覆盖它,但是如果我们只是想改一下回退按钮的样式,我们可以使用 headerBackTitleheaderBackTitleStyle, 和 headerBackImageSource

  1. 其他 options 可以参考 这里

标签导航器 Tab Navigator

npm install @react-navigation/bottom-tabs

使用createBottomTabNavigator, createMaterialBottomTabNavigator,createMaterialTopTabNavigator 创建标签导航器的时候,会返回一个对象,这个对象包含2个属性:navigatorscreen

const Tab = createBottomTabNavigator();

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

我们可以看到它的用法基本和堆栈导航器一致,有一些 options 属性可能不太相同,比如修改标签 icon 的几个属性:tabBarIcontabBarActiveTintColortabBarInactiveTintColor。我们还可以通过 tabBarBadge 属性给标签添加角标。 其他更多的属性可以参考这里

注意: 如果我们希望在一些页面上显示底部标签栏,一些页面上不显示,最好的办法是使用 Stack Navigator 嵌套 Tab Navigator

抽屉导航器 Drawer Navigator

npm install @react-navigation/drawer

使用 createDrawerNavigator 创建抽屉导航器的时候,会从左侧弹出一个导航菜单,它返回一个对象,这个对象包含2个属性:navigatorscreen

const Drawer = createDrawerNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Drawer.Navigator initialRouteName="Home">
        <Drawer.Screen name="Home" component={HomeScreen} />
        <Drawer.Screen name="Notifications" component={NotificationsScreen} />
      </Drawer.Navigator>
    </NavigationContainer>
  );
}

导航器嵌套

Stack NavigatorTab NavigatorDrawer Navigator这三种导航器可以嵌套使用,比如下面的例子中使用 Stack Navigator 嵌套 Tab Navigator

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

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

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

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

function Home() {
  return (
    <Tab.Navigator>
      <Tab.Screen name="Feed" component={Feed} />
      <Tab.Screen name="Messages" component={Messages} />
    </Tab.Navigator>
  );
}

export default 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>
  );
}

那么在这些导航器嵌套使用的时候会遵循以下守则:

  1. 每个导航器保存自己的导航历史 当我们在 Feed 页面 navigation.navigate('Profile'),再从 Profile 页面 navigation.navigate('Messages'),最后在 Messages 页面 navigation.goBack() 后,会回到 Feed 页面而不是 Profile 页面
  2. 每个导航器都有自己的选项 options
  3. 导航器中的每个屏幕都有自己的参数 params
  4. 导航操作由当前导航器处理,如果无法处理就会冒泡 当我们持续调用 navigation.goBack() 之后,退到了当前导航器的第一个历史记录,那么当前导航器就不能处理 goBack 操作了,所以它会向上冒泡,去执行父级导航器的 goBack
  5. 父导航器中分派动作给嵌套的子导航器,要使用 navigation.dispatch
  6. 子导航器不会接收父导航器的事件
  7. 父导航器的 UI 呈现在子导航器之上
  8. 导航到 Tab NavigatorDrawer Navigator 中的指定页面,当我们在 Profile 页面导航到 Home 中的 Messages 时,有两种写法:
navigation.navigate('Messages')
navigation.navigate('Home', { screen: 'Messages' })
navigation.navigate('Home', { screen: 'Messages'params: { user: 'jane' } })

导航器生命周期

可以使用 react 中本来就有的 componentDidMountcomponentWillUnmount 生命周期判断页面的转跳。当堆栈导航器中有 AB 两个页面,当 navigatorA 之后,会触发 AcomponentDidMount,当 pushB 的时候会触发 BcomponentDidMount,但是此时 A 还在历史堆栈中,所以不会触发 AcomponentWillUnmount,当我们再从 B 页面 goBackA 时,会触发 BcomponentWillUnmount,但不会触发 AcomponentDidMount

可以使用 useFocusEffect 或者 useIsFocused 在页面聚焦时进行操作。