react-navigation 6.x学习(2)

标题栏按钮

在头部添加按钮

与header交互最常见的方式是点击标题左边或右边的按钮。让我们在头部的右侧添加一个按钮(这是整个屏幕上最难触摸的地方,取决于手指和手机的大小,但也是放置按钮的正常位置)。

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{
          headerTitle: (props) => <LogoTitle {...props} />,
          headerRight: () => (
            <Button
              onPress={() => alert('This is a button!')}
              title="Info"
              color="#fff"
            />
          ),
        }}
      />
    </Stack.Navigator>
  );
}

当我们这样定义按钮时,options中的this变量不是HomeScreen实例,所以你不能调用setState或它的任何实例方法。但是,想让标题中的按钮与标题所属的屏幕交互是非常常见的。那么,我们接下来看看如何做到这一点。

header与组件的交互

想要和屏幕组件交互,需要使用navigation.setOptions来定义按钮。在组件中使用navigation.setOptions,我们可以访问屏幕的props,state,conext等。

function StackScreen() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={({ navigation, route }) => ({
          headerTitle: (props) => <LogoTitle {...props} />,
        })}
      />
    </Stack.Navigator>
  );
}

function HomeScreen({ navigation }) {
  const [count, setCount] = React.useState(0);

  React.useLayoutEffect(() => {
    navigation.setOptions({
      headerRight: () => (
        <Button onPress={() => setCount((c) => c + 1)} title="Update count" />
      ),
    });
  }, [navigation]);

  return <Text>Count: {count}</Text>;
}

自定义返回按钮

createNativeStackNavigator为后退按钮在特定平台上提供默认值。在iOS上,这包括 在按钮旁边的一个标签,当标题适合可用空间时,它会显示上一个屏幕的标题,否则它会显示“后退”。

你可以用headerBackTitle改变标签行为,用headerBackTitleStyle设置样式。

要定制后退按钮图片,可以使用headerBackImageSource

嵌套navigators

嵌套导航就是在一个Screen中再渲染另一个Screen

例如

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

在上面的例子中,在Home组件包含一个Tab 导航,而Home组件本身在App组件的stack导航中,所以一个tab导航被嵌套在stack导航中。

Stack.Navigator

  • Home(Tab.Navigator)
    • Feed (Screen)
    • Messages (Screen)
  • Profile (Screen)
  • Settings (Screen)

嵌套导航器的工作原理很像嵌套常规组件。为了实现您想要的行为,通常需要嵌套多个导航器。

嵌套导航需要注意

1.每个导航器都保留自己的导航历史

例如,当您在嵌套堆栈导航器的屏幕内按下后退按钮时,它将返回嵌套堆栈中的前一个屏幕,即使有另一个导航器作为父导航器。

2.每个导航都有自己的options

例如,在子导航器嵌套的屏幕中指定标题 options,不会影响父导航器中显示的标题。

3.导航器(navigator)中的每个屏幕(Screen)都有自己的参数

例如,在嵌套导航器中传递给屏幕的任何参数都在该屏幕的路由支柱中,不能从父或子导航器的屏幕访问。

如果需要从子屏幕访问父屏幕的参数,可以使用React Context向子屏幕公开参数。

4.导航操作由当前导航器处理,如果不能处理,就会弹出

5.导航器特定的方法在嵌套的导航器中可用

6.嵌套导航器不接收父级事件

7.父导航器的UI呈现在子导航器之上

在嵌套导航器中导航到屏幕

例子

function Root() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Home" component={Home} />
      <Drawer.Screen name="Profile" component={Profile} />
      <Stack.Screen name="Settings" component={Settings} />
    </Drawer.Navigator>
  );
}

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

从Feed组件跳转到Root组件

navigation.navigate('Root');

它跳转了,并显示了Root组件内的初始屏幕,即Home。但有时您可能想要控制导航时应该显示的屏幕。为了实现它,你可以在参数中传递屏幕的名称:

navigation.navigate('Root', { screen: 'Profile' });

现在,跳转到的是Profile组件而不是Home组件。

在嵌套导航器中向屏幕传递参数

可以使用params属性传递参数:

navigation.navigate('Root', {
  screen: 'Profile',
  params: { user: 'jane' },
});

深层嵌套:

navigation.navigate('Root', {
  screen: 'Settings',
  params: {
    screen: 'Sound',
    params: {
      screen: 'Media',
    },
  },
});

呈现导航器中定义的初始路由

如果你需要渲染导航器中指定的初始路由,你可以通过设置initial: false来禁用

navigation.navigate('Root', {
  screen: 'Settings',
  initial: false,
});
嵌套多个navigators

有时嵌套多个导航器(如堆栈、抽屉或标签)是很有用的。

当嵌套多个堆栈、抽屉或底部选项卡导航器时,子导航器和父导航器的标题都会显示出来。然而,通常更可取的做法是在子导航器中显示标题,而在父导航器的屏幕中隐藏标题。

为了实现这一点,您可以使用headerShown: false选项在包含导航器的屏幕中隐藏标题。

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

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

如果你不想在任何导航器中出现头信息,你可以在所有导航器中指定headerShown: false:

function Home() {
  return (
    <Tab.Navigator screenOptions={{ headerShown: false }}>
      <Tab.Screen name="Profile" component={Profile} />
      <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
  );
}

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

我们建议将嵌套导航器减少到最少。尽量用最少的嵌套来实现您想要的行为。嵌套有很多缺点:

它导致深度嵌套的视图层次结构,这可能会导致低端设备的内存和性能问题

嵌套相同类型的导航器(例如标签嵌在标签中,抽屉嵌在抽屉中等)可能会导致用户体验混乱

由于过度嵌套,在导航到嵌套屏幕、配置深度链接等时,代码变得难以遵循。

将嵌套导航器看作是实现您想要的UI的一种方法,而不是组织代码的一种方法。如果您想为组织创建单独的一组屏幕,而不是使用单独的导航器,您可以使用group组件。

<Stack.Navigator>
  {isLoggedIn ? (
    // Screens for logged in users
    <Stack.Group>
      <Stack.Screen name="Home" component={Home} />
      <Stack.Screen name="Profile" component={Profile} />
    </Stack.Group>
  ) : (
    // Auth screens
    <Stack.Group screenOptions={{ headerShown: false }}>
      <Stack.Screen name="SignIn" component={SignIn} />
      <Stack.Screen name="SignUp" component={SignUp} />
    </Stack.Group>
  )}
  {/* Common modal screens */}
  <Stack.Group screenOptions={{ presentation: 'modal' }}>
    <Stack.Screen name="Help" component={Help} />
    <Stack.Screen name="Invite" component={Invite} />
  </Stack.Group>
</Stack.Navigator>

导航的生命周期

如果你是在一个web后台使用反应式导航,你可以假设当用户从路由a导航到路由B时,a会卸载(它的组件willunmount被调用),当用户返回时,a会再次挂载。虽然这些React生命周期方法仍然有效,并用于反应式导航,但它们的用法不同于web。这是由更复杂的移动导航需求驱动的。

与 React 相关联的生命周期

假设在 stack navigator 中有,页面A,页面B。

  • 进入页面A,调用 A.componentDidMount
  • 进入页面B,调用 B.componentDidMount
  • 离开页面B,调用 B.componentWillUnmount
  • 离开页面A,调用 A.componentWillUnmount

实例场景

考虑一个带有屏幕a和b的堆栈导航器。在导航到a之后,它的componentDidMount被调用。当push B时,它的componentDidMount也被调用,但A仍然挂载在堆栈上,因此它的componentWillUnmount不被调用。

当从B返回到A时,B的componentWillUnmount被调用,但A的componentDidMount不被调用,因为A一直处于挂载状态。

function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator>
        <Tab.Screen name="First">
          {() => (
            <SettingsStack.Navigator>
              <SettingsStack.Screen
                name="Settings"
                component={SettingsScreen}
              />
              <SettingsStack.Screen name="Profile" component={ProfileScreen} />
            </SettingsStack.Navigator>
          )}
        </Tab.Screen>
        <Tab.Screen name="Second">
          {() => (
            <HomeStack.Navigator>
              <HomeStack.Screen name="Home" component={HomeScreen} />
              <HomeStack.Screen name="Details" component={DetailsScreen} />
            </HomeStack.Navigator>
          )}
        </Tab.Screen>
      </Tab.Navigator>
    </NavigationContainer>
  );
}

我们从Home页面导航到Details页面,然后我们使用Tab切换到Settings,在2跳转到Profile。完成这一系列操作后,4个Sceen都被挂载。如果你使用标签栏切换回Home,你会注意到你会出现DetailsScreen,HomeStack的导航状态被保留了!

导航生命周期事件

现在,让我们来回答我们在开始时提出的问题:“我们如何发现用户是离开(模糊)它还是回到(聚焦)它?”

React Navigation向订阅事件的屏幕组件发出事件。我们可以通过监听focus和blur事件来知道屏幕分别何时聚焦或失焦。

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

    return unsubscribe;
  }, [navigation]);

  return <ProfileContent />;
}

我们可以使用useFocusEffect钩子来执行副作用,不用手动添加事件监听器。它类似于React的useEffect钩子,但它与导航生命周期相关联。

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

function Profile() {
  useFocusEffect(
    React.useCallback(() => {
      // Do something when the screen is focused

      return () => {
        // Do something when the screen is unfocused
        // Useful for cleanup functions
      };
    }, [])
  );

  return <ProfileContent />;
}

如果你想根据屏幕是否聚焦来呈现不同的东西,你可以使用useIsFocused钩子,它返回一个布尔值来指示屏幕是否聚焦。

总结

1.虽然React的生命周期方法仍然有效,但React Navigation添加了更多的事件,您可以通过navigation prop订阅这些事件。

2.你也可以使用useFocusEffect或useIsFocused钩子。

术语表

Navigator

Navigator是React组件,它决定如何呈现已定义的屏幕。它包含Screen元素作为子元素,用于定义屏幕的配置。

NavigationContainer是一个管理导航树并包含导航状态的组件。该组件必须封装所有导航器结构。通常,我们会在应用的根目录渲染这个组件,它通常是从app .js导出的组件。

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

路由器是一组函数的集合,它们决定如何处理导航器中的操作和状态变化(类似于Redux应用程序中的reducer)。通常情况下,您永远不需要与路由器直接交互,除非您正在编写一个自定义导航器。

Screen component

屏幕组件是我们在路由配置中使用的组件。

const Stack = createNativeStackNavigator();

const StackNavigator = (
  <Stack.Navigator>
    <Stack.Screen
      name="Home"
      component={HomeScreen} // <----
    />
    <Stack.Screen
      name="Details"
      component={DetailsScreen} // <----
    />
  </Stack.Navigator>
);

组件名称中的Screen后缀完全是可选的,但这是一种常用的约定;我们可以叫它迈克尔,这也是一样的。

Navigation Prop

这个属性将被传递给所有Screen,它可以用于以下用途:

1.Dispatch将向路由器发送一个动作

2.navigate, goBack等可以方便地调度动作

3.导航器也可以接受导航道具,如果有的话,它们应该从父导航器中获得。

Route Prop

这个属性会传送到所有屏幕。包含当前路由的信息,如params, key and name

Navigation State
{
  key: 'StackRouterRoot',
  index: 1,
  routes: [
    { key: 'A', name: 'Home' },
    { key: 'B', name: 'Profile' },
  ]
}

对于这种导航状态,有两个路由(可能是选项卡,也可能是堆栈中的卡片)。索引表示活动路由,为B。

Route

每个路由都是一个对象,它包含一个用来标识它的键和一个用来指定路由类型的“名称”。它也可以包含任意参数:

{
  key: 'B',
  name: 'Profile',
  params: { id: '123' }
}
Header

也就是导航头,导航栏,应用栏,还有很多其他东西。这是屏幕顶部的矩形,包含后退按钮和屏幕标题。整个矩形通常被称为React Navigation中的标题。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值