likes
comments
collection
share

RN 之react-navigation

作者站长头像
站长
· 阅读数 1

首先添加核心依赖库

# 安装导航的核心库
yarn add @react-navigation/native

# 安装导航的外部依赖库
yarn add react-native-screens react-native-safe-area-context

# 安装堆栈导航的主要库
yarn add @react-navigation/native-stack

基本的导航有两种,一个是 NativeStac 一个是BottomTab,就相当于 iOS 里面的 UINavigationConctroller 和 UITabbarController,实现一般的导航效果,都是这两种搭配着使用

无论哪种导航方式,最终都要先包裹在 NavigationContainer 里面,有点类似于Vue的:

export default function App() {
  return (
    <NavigationContainer>
      {/* 导航组件 */}
    </NavigationContainer>
  );
}

native-stack

主要用到createNativeStackNavigator函数,用于配置堆栈路由的管理;

它包含两个组件的对象:Screen和Navigator,他们都是配置导航器所需的React组件,其中Screen组件是一个高阶组件,会增强props;在使用的页面中,会携带navigation对象和route对象,Navigator 则代表一个堆栈,下面我们会介绍这两个对象的用法。

简单用法

// StackRouter.js
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import * as React from 'react';
import {View, Text} from 'react-native';

const Stack = createNativeStackNavigator();
function HomeScreen() {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text>Home Screen</Text>
    </View>
  );
}
export default function StackRouter() {
  return (
    <Stack.Navigator>
      <Stack.Screen
          name="Home"
          component={HomeScreen}
      />
    </Stack.Navigator>
  );
}

在根组件中引入我们的堆栈导航组件即可:

// App.js
import StackRouter from './src/StackRouter';
export default function App() {
  return (
    <NavigationContainer>
      <StackRouter></StackRouter>
    </NavigationContainer>
  );
}

RN 之react-navigation

如果还有其他界面

// StackRouter.js
export default function StackRouter() {
  return (
    <Stack.Navigator initialRouteName="Home">
      <Stack.Screen
          name="Home"
          component={HomeScreen}
      />
      <Stack.Screen
          name="List"
          component={ListScreen}
      />
      <Stack.Screen
          name="Detail"
          component={DetailScreen}
      />
    </Stack.Navigator>
  );
}

RN 之react-navigation

这里代表导航控制器里面后三个页面,分别是Home、List、Detail,可以在彼此之间跳转,就像 Vue 里面配置的路由一样

导航栏配置

  • headerStyle:整个标题的样式,可以设置backgroundColor背景颜色。
  • headerTintColor:返回按钮和标题文字的颜色。
  • headerTitleStyle:标题文字的样式,可以设置fontFamily和fontWeight

如果想要更改当前导航控制器的样式,可以使用:,

export default function StackRouter() {
  return (
    <Stack.Navigator
      initialRouteName="Home"
      screenOptions={{
        headerTintColor: 'blue',
        headerStyle: {backgroundColor: '#f4511e'},
        headerTitleStyle: {
          fontWeight: 'bold',
        },
      }}>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={({navigation, route}) => {
          return {
            title: '首页',
            headerStyle: {
              height: 80,
              backgroundColor: '#2196F3',
            },
            headerTitle: headerTitleComponent,
            headerRight: RightViewComponent,
            headerShown: true,
            headerTitleAlign: 'center',
            headerTitleStyle: {
              color: '#fff',
            },
            headerBackTitle: '返回',
            headerBackTitleStyle: {
              color: '#fff',
            },
          };
        }}
      />
    </Stack.Navigator>
  );
}

在Stack.Navigator 上添加的样式是当前导航堆栈内共享的样式,在Stack.Screen 上设置的样式回覆盖Stack.Navigator 上添加的样式

如果想在当前页面上根据情况修改样式,可以使用

const headerTitleComponent = () => <Button title="Setting">1</Button>;
...

<Button
  title="Update"
  onPress={() =>
    navigation.setOptions({
      title: 'New Home', //更改当前页面标题
      headerRight: headerTitleComponent,
    })
  }>
  1
</Button>

页面跳转

Hook

简单认识两个 Hook,useNavigation 和 useRoute

其中前者用来做页面跳转,后者是获取当前堆栈路由参数的

跳转&传参

<Button
  title="跳转到 List"
  onPress={() =>
    navigation.navigate('List', {
      id: 86,
      otherParam: 'anything you want here',
    })
  }
  />

这里点击按钮之后就会跳转到导航器对应的 List 页面上,,并传递两个参数,可使用如下方式接受,如果想接受之后再次更改参数,可以使用navigation.setParams

function ListScreen({navigation, route}) {
  const {id, otherParam} = route.params;
  React.useEffect(() => {
    if (id) {
      // Post updated, do something with `route.params.post`
      // For example, send the post to the server
      setTitle('useEffect执行');
    }
  }, [id]);

  // 页面也能更新自己的参数,就像更新state状态一样;通过navigation.setParams进行更新:
  const changeParam = () => { 
    navigation.setParams({
      otherParam: 'someText',
    });
  };

  const backToTop = () => {
    navigation.popToTop();
  };
  const [title, setTitle] = React.useState('');

  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Button
        title="跳转到 Detail"
        onPress={() => navigation.navigate('Detail')}
      />
      <Text style={{margin: 10}}>Post: {id}</Text>
      <Text style={{margin: 10}}>Post: {otherParam}</Text>
      <Text style={{margin: 10}}>title: {title}</Text>
      <Button title="Change param" onPress={changeParam} />
      <Button title="backToTop" onPress={backToTop} />
    </View>
  );
}

有时候堆栈导航的层级很深,我们需要穿越好几个页面才能返回到第一个页面;在这种情况下如果我们明确的知道我们想要回到的是Home页,我们可以直接调用navigation.navigate('Home'),清除所有的路由并且回到Home。

导航的生命周期

使用useIsFocused Hook 可以检检测当前的页面生命周期,根据屏幕当前的焦点状态呈现不同的内容

或者使用

export default class Screen extends Component {
  constructor(props) {
    super(props);
  }
  componentDidMount(){
    this.props.navigation.addListener('focus', () => {
      console.log('页面进入');
    });
    this.props.navigation.addListener('blur', () => {
      console.log('页面离开');
    });
  }
}

navigation支持以下五种事件:

  • focus:当屏幕聚焦时。
  • blur:当屏幕失去焦点时。
  • state:当导航器的状态改变时,通过event接收新的状态(event.data.state)。
  • beforeRemove:当用户要离开页面时,可以阻止用户离开。
  • tabPress:点击tab页面切换

navigation.addListener返回一个函数,可以在组件销毁时调用来取消订阅的事件:

export default class Screen extends Component {
  constructor(props) {
    super(props);
  }
  componentDidMount(){
    this._focus = this.props.navigation.addListener('focus', () => {
      // ....
    });
  }
  componentWillUnmount(){
    this._focus()
  }
}

bottom-tabs

RN 之react-navigation

React Natigation中主要使用的选项卡导航就是@react-navigation/bottom-tabs,其他的还有下面这种material风格的:

RN 之react-navigation

material风格主要是@react-navigation/material-bottom-tabs 和 @react-navigation/material-top-tabs,使用方法都大同小异,只是风格不同,需要和App整体的风格协调。我们回到bottom-tabs,首先需要进行安装:

npm install @react-navigation/bottom-tabs
# 或者
yarn add @react-navigation/bottom-tabs

我们看下bottom-tabs的简单使用案例:

const Tab = createBottomTabNavigator();
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';

class TabRouter extends Component {
  render() {
    return (
      <Tab.Navigator>
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="List" component={ListScreen} />
      </Tab.Navigator>
    )
  }
}

这样我们看到底部多了两个tab按钮,但是没有icon,比较简陋;这里引入react-native-vector-icons这个库,包含很多icon图标。遵循安装教程安装好后,我们在它的主页上,找到我们需要的icon,这里包含了AntDesign、FontAwesome、Ionicons、MaterialIcons等一众丰富的图标。

import Ionicons from 'react-native-vector-icons/Ionicons';

<Tab.Navigator
  screenOptions={({route}) => ({
    tabBarActiveTintColor: 'tomato',
    tabBarInactiveTintColor: 'gray',
    tabBarIcon: ({focused, color, size}) => {
      let iconName;
      if (route.name === 'Home') {
        iconName = focused ? 'home': 'home-outline';
      }
      if (route.name === 'List') {
        iconName = focused ? 'list-circle': 'list-circle-outline';
      }
      return <Ionicons name={iconName} size={size} color={color} />;
    },
  })}>
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="List" component={ListScreen} />
</Tab.Navigator>

RN 之react-navigation

属性

背景

注意;如果要更改 tabbar 背景颜色,需要使用属性:

tabBarBackground: () => (
  <View
    style={{backgroundColor: 'pink', width: '100%', height: '100%'}}
    />
),



  export default function TabStackRouter({navigation}) {
  return (
    <Tab.Navigator
      screenOptions={({route}) => ({
        tabBarActiveTintColor: 'pink',
        tabBarInactiveTintColor: 'gray',
        tabBarActiveBackgroundColor: 'yellow',
        tabBarIcon: ({focused, color, size}) => {
          let iconName;
          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          }
          if (route.name === 'List') {
            iconName = focused ? 'list-circle' : 'list-circle-outline';
          }
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarBackground: () => (
          <View
            style={{backgroundColor: 'pink', width: '100%', height: '100%'}}
            />
        ),
        tabBarStyle: () => <View style={{backgroundColor: 'black'}} />,
      })}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{headerShown: false, tabBarActiveBackgroundColor: 'red'}}
        />
      <Tab.Screen
        name="ListStack"
        component={ListStackScreen}
        options={{
          headerShown: false,
        }}
        />
      <Tab.Screen name="List" component={ListScreen} />
    </Tab.Navigator>
  );
}

徽章(Badge)

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

RN 之react-navigation

组合使用

/* eslint-disable react-native/no-inline-styles */
/* eslint-disable react/no-unstable-nested-components */
import Ionicons from 'react-native-vector-icons/Ionicons';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import * as React from 'react';
import {Button, View, Text, Image, StyleSheet} from 'react-native';

import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
// 创建 tabbar 
const Tab = createBottomTabNavigator();

// 创建 navigator
const ListStack = createNativeStackNavigator();

// 主页
function HomeScreen({navigation, route}) {
  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'red',
      }}>
      <Button
        title="跳转到 List"
        onPress={() =>
          navigation.navigate('List', {
            id: 86,
            otherParam: 'anything you want here',
          })
        }
      />
    </View>
  );
}

// 列表页
function ListScreen({navigation, route}) {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Button
        title="ListScreen"
        onPress={() =>
          navigation.navigate('Detail', {
            id: 86,
            otherParam: 'anything you want here',
          })
        }
      />
    </View>
  );
}

//详情页
function Detail({navigation, route}) {
  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text> Detail </Text>
    </View>
  );
}

// export default function TabNavigatorScreen() {
//   return (
//     <Tab.Navigator
//       screenOptions={({route}) => ({
//         tabBarActiveTintColor: 'black',
//         tabBarInactiveTintColor: 'gray',
//         tabBarIcon: ({focused, color, size}) => {
//           let iconName;
//           if (route.name === 'Home') {
//             iconName = focused ? 'home' : 'home-outline';
//           }
//           if (route.name === 'List') {
//             iconName = focused ? 'list-circle' : 'list-circle-outline';
//           }
//           return <Ionicons name={iconName} size={size} color={color} />;
//         },
//       })}>
//       <Tab.Screen
//         name="Home"
//         component={HomeScreen}
//         options={{tabBarBadge: 3}}
//       />
//       <Tab.Screen name="List" component={ListScreen} />
//     </Tab.Navigator>
//   );

// const Tab = createBottomTabNavigator();

// navigator 导航
function ListStackScreen() {
  return (
    <ListStack.Navigator>
      <ListStack.Screen name="List" component={ListScreen} />
      <ListStack.Screen name="Detail" component={Detail} />
    </ListStack.Navigator>
  );
}
// 将 navigator 嵌套到 tabbar 里面,并设置 tabbar 样式
export default function TabStackRouter({navigation}) {
  return (
    <Tab.Navigator
      screenOptions={({route}) => ({
        tabBarActiveTintColor: 'pink',
        tabBarInactiveTintColor: 'gray',
        tabBarActiveBackgroundColor: 'yellow',
        tabBarIcon: ({focused, color, size}) => {
          let iconName;
          if (route.name === 'Home') {
            iconName = focused ? 'home' : 'home-outline';
          }
          if (route.name === 'List') {
            iconName = focused ? 'list-circle' : 'list-circle-outline';
          }
          return <Ionicons name={iconName} size={size} color={color} />;
        },
        tabBarBackground: () => (
          <View
            style={{backgroundColor: 'pink', width: '100%', height: '100%'}}
          />
        ),
        tabBarStyle: () => <View style={{backgroundColor: 'black'}} />,
      })}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{headerShown: false, tabBarActiveBackgroundColor: 'red'}}
      />
      <Tab.Screen
        name="ListStack"
        component={ListStackScreen}
        options={{
          headerShown: false,
        }}
      />
      <Tab.Screen name="List" component={ListScreen} />
    </Tab.Navigator>
  );
}

抽屉式导航

抽屉式导航是从侧边栏划出抽屉一样的效果,抽屉式导航的核心是「隐藏」,突出核心功能,隐藏一些不必要的操作,比如一些简报、新闻、栏目等等;在小屏幕时代,内容篇幅展示有限,会把一些辅助的功能放到侧边栏里;但是随着屏幕尺寸越来越大,通过新的交互方式,我们的App已经能够容纳足够多的内容,抽屉式导航的缺点也逐渐暴露:交互效率低下,处于操作盲区,单手操作不便。

因此抽屉式导航也逐渐衰落了,我们在大多App中也很难看到这种导航方式,仅有少数还保留,我们看下在虎嗅App上使用抽屉式导航的效果:

RN 之react-navigation

虎嗅App的抽屉式导航

要在我们的App中使用这种导航,我们首先安装几个依赖:

yarn add @react-navigation/drawer
yarn add react-native-gesture-handler react-native-reanimated

和其他两种导航方式相同,我们需要把创建函数从依赖中导出:

import { createDrawerNavigator } from '@react-navigation/drawer';

const Drawer = createDrawerNavigator();

function DrawerRouter() {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="Home" component={HomeScreen} />
      <Drawer.Screen name="List" component={ListScreen} />
    </Drawer.Navigator>
  );
}

我们可以点击左上角的按钮展开抽屉,或者在页面调用如下API进行手动打开或关闭:

navigation.openDrawer();
navigation.closeDrawer();
navigation.toggleDrawer();

还可以定义drawerPosition,设置打开抽屉的位置,支持left(默认)和right:

<Drawer.Navigator
  screenOptions={{
    drawerPosition: 'right',
  }}>
  ...
</Drawer.Navigator>

我们可以设置打开抽屉的内容,通过drawContentView很容易的覆写内容。默认情况下在ScrollView下渲染了一个DrawerItemList元素,在这基础上我们可以进行自定义,也可以使用DrawerItem元素:

import {
  DrawerContentScrollView,
  DrawerItemList,
  DrawerItem,
} from '@react-navigation/drawer';

function drawContentView(props) {
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      {/* 这里加入想要新增的一些元素 */}
      <DrawerItem label="Help" onPress={...} />
    </DrawerContentScrollView>
  );
}

function DrawerRouter() {
  return (
    <Drawer.Navigator
      drawerContent={props => drawContentView(props)}>
      ...
    </Drawer.Navigator>
  );
}

RN 之react-navigation

抽屉式导航效果

引用

前端壹读zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760zhuanlan.zhihu.com/c_1188483126786293760

更多文章,以及更好的阅读体验敬请访问博客地址

知乎 - 安全中心xieyufei.comxieyufei.comxieyufei.comxieyufei.comxieyufei.com

React-Native导航组件React-Navigation的详细介绍_react native navigation-CSDN博客

react-navigation 6.x 学习(3)_@react-navigation/bottom-tabs-CSDN博客

深入学习React Native之路由导航