在上一篇介绍了 React Native 项目的初始化方式选择后,我选择了Cli的方式创建了项目。接下来就是实现 React Native 的页面跳转。
使用Cli创建项目
            
            
              bash
              
              
            
          
          npx @react-native-community/cli@latest init AwesomeProject
        按照 React Native 官方文档安装所需依赖
reactnative.dev/docs/naviga...
            
            
              bash
              
              
            
          
          yarn add @react-navigation/native @react-navigation/native-stack
        
            
            
              bash
              
              
            
          
          yarn add react-native-screens react-native-safe-area-context
        运行 ios 前,需要先执行
            
            
              bash
              
              
            
          
          cd ios
pod install
cd ..
        修改 App.tsx,创建导航容器
            
            
              tsx
              
              
            
          
          import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import RootStack from '@/navigation';
function App(): React.JSX.Element {
  return (
    <NavigationContainer>
      <RootStack />
    </NavigationContainer>
  );
}
export default App;
        这里我把 RootStack 放到 navigation 目录下,为了方便管理。
为了使用@作为 src 目录别名,需要安装下面的依赖并做一下配置。
            
            
              bash
              
              
            
          
          yarn add --dev babel-plugin-module-resolver
        在 babel.config.js 文件中添加下面 plugins 配置
            
            
              javascript
              
              
            
          
          module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    [
      'module-resolver',
      {
        root: ['./src'],
        alias: {
          '@': './src',
        },
      },
    ],
  ],
};
        为了让 vscode 能够友好的提示,在 tsconfig.json 文件中新增 compilerOptions 配置
            
            
              json
              
              
            
          
          {
  "extends": "@react-native/typescript-config/tsconfig.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
        继续完成 RootStack
创建 navigation 模块,导出 RootStack
            
            
              tsx
              
              
            
          
          import React from 'react';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import HomeScreen from '@/screens/HomeScreen';
import ProfileScreen from '@/screens/ProfileScreen';
const Stack = createNativeStackNavigator();
function RootStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen
        name="Home"
        component={HomeScreen}
        options={{title: 'Welcome'}}
      />
      <Stack.Screen name="Profile" component={ProfileScreen} />
    </Stack.Navigator>
  );
}
export default RootStack;
        创建 HomeScreen、ProfileScreen
            
            
              tsx
              
              
            
          
          import React from 'react';
import {Button, Text, View} from 'react-native';
const HomeScreen = ({navigation}) => {
  return (
    <View>
      <Text> HomeScreen </Text>
      <Button
        title="Go to Jane's profile"
        onPress={() => {
          navigation.navigate('Profile', {name: 'Jane'});
        }}
      />
    </View>
  );
};
export default HomeScreen;
        
            
            
              tsx
              
              
            
          
          import React from 'react';
import {Text} from 'react-native';
const ProfileScreen = ({route}) => {
  return <Text>This is {route.params.name}'s profile</Text>;
};
export default ProfileScreen;
        运行查看效果:
如果运行 ios,如果安装了原生相关的依赖,都需要再重新运行前,执行一下 ios 相关的命令
            
            
              bash
              
              
            
          
          npx pod-install
        或
            
            
              bash
              
              
            
          
          cd ios
pod install
cd ..
        然后运行 React Native 启动命令
            
            
              bash
              
              
            
          
          yarn start
        建议再开一个终端窗口,执行启动模拟器
            
            
              bash
              
              
            
          
          yarn ios
# 或
yarn android
        演示效果:
 到此已在 App 中完成最基本的路由导航
路由导航的 TS 类型
在上面的例子中,发现部分代码的 ts 类型报错
下面继续完善路由导航,添加类型文件
在 navigation 模块下创建 type.ts,导出 RootStackParamList 类型
            
            
              ts
              
              
            
          
          // src/navigation/type.ts
import {NativeStackScreenProps} from '@react-navigation/native-stack';
export type RootStackParamList = {
  Home: undefined;
  Profile: {name: string};
};
export type HomeScreenProps = NativeStackScreenProps<
  RootStackParamList,
  'Home'
>;
export type ProfileScreenProps = NativeStackScreenProps<
  RootStackParamList,
  'Profile'
>;
declare global {
  namespace ReactNavigation {
    /**
     * 全局导航参数列表
     */
    interface RootParamList extends RootStackParamList {}
  }
}
        修改 navigation/index.tsx ,给 createNativeStackNavigator 方法添加 RootStackParamList 类型
            
            
              ts
              
              
            
          
          const Stack = createNativeStackNavigator<RootStackParamList>();
        修改 HomeScreen、ProfileScreen,添加各自的类型
            
            
              tsx
              
              
            
          
          const HomeScreen = ({navigation}: HomeScreenProps) => {
  ...
};
        
            
            
              tsx
              
              
            
          
          const ProfileScreen = ({route}: ProfileScreenProps) => {
  ...
};
        现在已经没有了 screen 的 ts 类型报错了,并且在使用导航器的 navigation 和 route 的时候,还会有友好的代码提示。
Tabs 导航 和 Drawer 导航
一般 app 首页都会有底部 Tabs 导航和 Drawer 导航,下面通过修改前面的代码,将 HomeScreen 和 ProfileScreen 改为 Tabs 导航页面,并添加 Drawer
安装 tabs 导航依赖
            
            
              bash
              
              
            
          
          yarn add @react-navigation/bottom-tabs @react-navigation/drawer
        要使用 @react-navigation/drawer,还需要额外安装下面的依赖
            
            
              bash
              
              
            
          
          yarn add react-native-gesture-handler react-native-reanimated
        创建首页底部 Tabs 导航
创建下面页面,并把之前的 HomeScreen 和 ProfileScreen 移动到 HomeTabs 目录下
            
            
              tsx
              
              
            
          
          // src/screen/HomeTabs/index.tsx
import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
import {HomeTabsParamList} from '@/navigation/types';
const BottomTabNavigator = createBottomTabNavigator<HomeTabsParamList>();
const HomeTabs = () => {
  return (
    <BottomTabNavigator.Navigator initialRouteName="Home">
      <BottomTabNavigator.Screen
        name="Home"
        component={HomeScreen}
        options={{title: '首页'}}
      />
      <BottomTabNavigator.Screen
        name="Profile"
        component={ProfileScreen}
        options={{title: '我的'}}
      />
    </BottomTabNavigator.Navigator>
  );
};
export default HomeTabs;
        修改导航根节点
            
            
              jsx
              
              
            
          
          import React from 'react';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {RootStackParamList, RoowDrawerStackParamList} from './types';
import HomeTabs from '../screens/HomeTabs/index';
import {createDrawerNavigator} from '@react-navigation/drawer';
import AboutScreen from '@/screens/Settings/AboutScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
const RootDrawerStack = createDrawerNavigator<RoowDrawerStackParamList>();
function RootStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="HomeTabs" component={HomeTabs} />
      <Stack.Group>
        <Stack.Screen name="About" component={AboutScreen} />
      </Stack.Group>
    </Stack.Navigator>
  );
}
function RootDrawerWrapper() {
  return (
    <RootDrawerStack.Navigator>
      <RootDrawerStack.Screen name="MainDrawer" component={RootStack} />
    </RootDrawerStack.Navigator>
  );
}
export default RootDrawerWrapper;
        此处新增了一个 About 页面
            
            
              jsx
              
              
            
          
          import {AboutScreenProps} from '@/navigation/types';
import {Text, View, Button} from 'react-native';
const AboutScreen = ({navigation}: AboutScreenProps) => {
  return (
    <View>
      <Text> AboutScreen </Text>
      <Button
        title="open main drawer"
        onPress={() => {
          navigation.openDrawer();
        }}
      />
    </View>
  );
};
export default AboutScreen;
        修改 navigation 的类型文件
            
            
              ts
              
              
            
          
          import {BottomTabScreenProps} from '@react-navigation/bottom-tabs';
import {DrawerScreenProps} from '@react-navigation/drawer';
import {
  CompositeScreenProps,
  NavigatorScreenParams,
} from '@react-navigation/native';
import {NativeStackScreenProps} from '@react-navigation/native-stack';
/**
 * 根堆栈导航的参数列表类型
 * @typedef RootStackParamList
 * @property {NavigatorScreenParams<HomeTabsParamList>} HomeTabs - 主页标签页导航参数
 * @property {undefined} About - 关于页面
 */
export type RootStackParamList = {
  HomeTabs: NavigatorScreenParams<HomeTabsParamList>;
  About: undefined;
};
/**
 * 根抽屉导航的参数列表类型
 * @typedef RoowDrawerStackParamList
 * @property {undefined} MainDrawer - 主抽屉页面
 */
export type RoowDrawerStackParamList = {
  MainDrawer: undefined;
};
/**
 * 根堆栈屏幕属性类型
 * 组合了原生堆栈导航属性和抽屉导航属性
 * @template T - 根堆栈参数列表中的键
 */
export type RootStackScreenProps<T extends keyof RootStackParamList> =
  CompositeScreenProps<
    NativeStackScreenProps<RootStackParamList, T>,
    DrawerScreenProps<RoowDrawerStackParamList>
  >;
/**
 * 主页标签页导航的参数列表类型
 * @typedef HomeTabsParamList
 * @property {undefined} Home - 主页
 * @property {undefined} Profile - 个人资料页
 */
export type HomeTabsParamList = {
  Home: undefined;
  Profile: undefined;
};
/**
 * 主页屏幕属性类型
 * 组合了底部标签页导航属性和根堆栈导航属性
 */
export type HomeScreenProps = CompositeScreenProps<
  BottomTabScreenProps<HomeTabsParamList, 'Home'>,
  RootStackScreenProps<'HomeTabs'>
>;
/**
 * 个人资料页屏幕属性类型
 * 组合了底部标签页导航属性和根堆栈导航属性
 */
export type ProfileScreenProps = CompositeScreenProps<
  BottomTabScreenProps<HomeTabsParamList, 'Profile'>,
  RootStackScreenProps<'HomeTabs'>
>;
/**
 * 关于页面的屏幕属性类型
 */
export type AboutScreenProps = RootStackScreenProps<'About'>;
declare global {
  namespace ReactNavigation {
    /**
     * 全局导航参数列表
     */
    interface RootParamList extends RootStackParamList {}
  }
}
        演示效果:
Modal 导航
业务场景中场景的模态框,比如登录的弹窗
在 react-navigation 中,模态框的创建跟普通页面一致
只需要在 screenOptions 里设置 presentation 属性
            
            
              tsx
              
              
            
          
          <Stack.Group screenOptions={{presentation: 'modal'}}>
  <Stack.Screen name="MyModal" component={ModalScreen} />
</Stack.Group>
        下面我们来创建一个登录的模态框效果
            
            
              tsx
              
              
            
          
          // screens/Login/OnePressLoginModal.tsx
import {OnePressLoginModalProps} from '@/navigation/types';
import {Button, StyleSheet, TouchableWithoutFeedback, View} from 'react-native';
const OnePressLoginModal = ({navigation}: OnePressLoginModalProps) => {
  return (
    <View style={styles.container}>
      <TouchableWithoutFeedback
        onPress={() => {
          navigation.goBack();
        }}>
        <View style={styles.mask} />
      </TouchableWithoutFeedback>
      <View style={styles.modal}>
        <Button
          title="一键登录"
          onPress={() => {
            console.log('一键登录');
          }}
        />
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-end',
  },
  mask: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  modal: {
    backgroundColor: 'white',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    padding: 16,
    height: '40%',
    justifyContent: 'center',
    alignItems: 'center',
  },
});
export default OnePressLoginModal;
        在根节点导航器内添加 OnePressLoginModal
            
            
              tsx
              
              
            
          
          function RootStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="HomeTabs" component={HomeTabs} />
      <Stack.Group>
        <Stack.Screen name="About" component={AboutScreen} />
      </Stack.Group>
      <Stack.Group
        screenOptions={{
          presentation: 'transparentModal',
          headerShown: false,
          animation: 'fade_from_bottom',
        }}>
        <Stack.Screen
          name="OnePressLoginModal"
          component={OnePressLoginModal}
          options={{
            contentStyle: {backgroundColor: 'transparent'},
          }}
        />
      </Stack.Group>
    </Stack.Navigator>
  );
}
        在首页添加按钮,触发跳转
            
            
              tsx
              
              
            
          
          <Button
  title="open one press login modal"
  onPress={() => {
    navigation.navigate('OnePressLoginModal');
  }}
/>
        演示效果:
美化导航
使用 iconfont 图标库,为底部 tab 导航添加图标
安装必要的依赖:
            
            
              bash
              
              
            
          
          yarn add react-native-svg
        图标转换工具:
            
            
              bash
              
              
            
          
          yarn add -D react-native-iconfont-cli-2
        react-native-iconfont-cli-2 是我 fork react-native-iconfont-cli 做的修改
react-native-iconfont-cli 好像已经没维护了,当前版本会出现 defaultProps 的报错
所以自己 fork 做了修复并发布为 react-native-iconfont-cli-2
初始化和使用方式与 react-native-iconfont-cli 一致。
修改底部 tab 导航图标与样式
在之前的代码中修改 HomeTabs 代码,添加 screenOptions 参数
            
            
              tsx
              
              
            
          
          import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import HomeScreen from './HomeScreen';
import ProfileScreen from './ProfileScreen';
import {HomeTabsParamList} from '@/navigation/types';
import {RouteProp} from '@react-navigation/native';
import {IconHome, IconProfile} from '@/components/Iconfont';
const BottomTabNavigator = createBottomTabNavigator<HomeTabsParamList>();
const HomeTabs = () => {
  return (
    <BottomTabNavigator.Navigator
      initialRouteName="Home"
      screenOptions={({route}) => {
        return {
          headerShown: false, // 隐藏头部
          tabBarActiveTintColor: '#000', // 选中颜色
          tabBarInactiveTintColor: '#999', // 未选中颜色
          tabBarActiveBackgroundColor: '#fff', // 选中背景色
          tabBarInactiveBackgroundColor: '#fff', // 未选中背景色
          tabBarLabelStyle: {
            fontSize: 14,
          },
          tabBarIcon: getTabBarIcon(route),
        };
      }}>
      <BottomTabNavigator.Screen
        name="Home"
        component={HomeScreen}
        options={{title: '首页'}}
      />
      <BottomTabNavigator.Screen
        name="Profile"
        component={ProfileScreen}
        options={{title: '我的'}}
      />
    </BottomTabNavigator.Navigator>
  );
};
const getTabBarIcon =
  (route: RouteProp<HomeTabsParamList, keyof HomeTabsParamList>) =>
  (props: {focused: boolean; color: string; size: number}) => {
    switch (route.name) {
      case 'Home':
        return <IconHome color={props.color} size={24} />;
      case 'Profile':
        return <IconProfile color={props.color} size={24} />;
      default:
        return null;
    }
  };
export default HomeTabs;
        效果:
优化刘海屏和底部小白条
当我们给 screenOptions 设置 headerShown: false 后
我们需要给页面做兼容处理
我们前面已经有安装了所需依赖:
react-native-safe-area-context
先给 app.tsx 加一层 SafeAreaProvider
            
            
              tsx
              
              
            
          
          import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import RootStack from '@/navigation';
import {SafeAreaProvider} from 'react-native-safe-area-context';
function App(): React.JSX.Element {
  return (
    <SafeAreaProvider>
      <NavigationContainer>
        <RootStack />
      </NavigationContainer>
    </SafeAreaProvider>
  );
}
export default App;
        然后在需要兼容的页面,加上一层 SafeAreaView
            
            
              tsx
              
              
            
          
          import {HomeScreenProps} from '@/navigation/types';
import {Button, StyleSheet, Text, View} from 'react-native';
import {SafeAreaView} from 'react-native-safe-area-context';
const HomeScreen = ({navigation}: HomeScreenProps) => {
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.content}>
        <Text> HomeScreen </Text>
        <Button
          title="open main drawer"
          onPress={() => {
            navigation.openDrawer();
          }}
        />
        <Button
          title="open AboutScreen"
          onPress={() => {
            navigation.navigate('About');
          }}
        />
        <Button
          title="open one press login modal"
          onPress={() => {
            navigation.navigate('OnePressLoginModal');
          }}
        />
      </View>
    </SafeAreaView>
  );
};
const styles = StyleSheet.create({
  container: {
    backgroundColor: 'red',
  },
  content: {
    backgroundColor: '#fff',
  },
});
export default HomeScreen;
        现在看刚刚的首页效果:
我们给容器加上背景后,可以看到上下默认都有做了间距处理
默认是使用 padding,也可以通过 mode 参数修改为 margin
在 tab 页面,底部已经由 tabBar 处理了,所以我们的容器不需要再处理底部的间距
通过 edges 参数,控制需要的方向
            
            
              tsx
              
              
            
          
          <SafeAreaView style={styles.container} edges={['top']}>
        效果:
参考资料
目前已完成的模板代码都在这了,有需要可自行查看。github.com/ace0109/rea...