React Navigation官网
社区今后主推的方案是一个单独的导航库react-navigation
,它的使用十分简单。React Navigation 中的视图是原生组件,同时用到了运行在原生线程上的Animated
动画库,因而性能表现十分流畅。此外其动画形式和手势都非常便于定制。
安装
首先执行以下命令安装 react-navigation
所需要的依赖
bash
yarn add @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context
yarn add @react-navigation/native-stack @react-navigation/bottom-tabs
@react-natigation/native
是路由的核心库react-native-screens
通过使用原生的导航控制器来替代 React Native 的默认导航器,从而提高屏幕切换的性能react-native-safe-area-context
用于处理不同设备上的安全区域(safearea)问题@react-navigation/native-stack
用于创建堆栈导航器@react-navigation/bottom-tabs
用于创建底部标签导航器
需要更多导航方式的可以查看 react-navigation
官网,里面还提供了 drawer
和modal
等多种导航方式
编写 Screen
接下来我们想要实现一个有着四个页面的项目,其中 home
和 my
是项目的 tabbar,在 home
页面可以跳转到 news/index
资讯列表页,在 news/index
点击一条资讯可以跳转到 news/detail
资讯详情页面。
首先,我们按照下图新增四个页面。

接下来我们按步骤来完善我们的项目代码
一、组件基础代码编写
screens/tabbar/home.tsx
tsx
import { Button, Text, TouchableOpacity, View } from "react-native";
type HomeProps = {};
const Home: React.FC<HomeProps> = ({
navigation
}) => {
return (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: 16,
}}
>
<Text>这里是 Home</Text>
<Button title="跳转到资讯列表" onPress={() => {
navigation.navigate("News", {
screen: "Index",
});
}}/>
</View>
);
};
export default Home;
screens/tabbar/my.tsx
tsx
import { Text, View } from "react-native";
type MyProps = {};
const My: React.FC<MyProps> = (props) => {
return (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: 16,
}}
>
<Text>这里是 My</Text>
</View>
)
};
export default My;
screens/news/index.tsx
tsx
import { ScrollView, Text, TouchableOpacity } from "react-native";
type NewsIndexProps = {};
const NewsIndex: React.FC<NewsIndexProps> = ({ navigation }) => {
const newsList = new Array(100).fill(0).map((_, index) => ({
id: index,
title: `新闻标题${index}`,
content: `新闻内容${index}`,
}));
return (
<ScrollView>
{newsList.map((news) => (
<TouchableOpacity
key={news.id}
style={{
padding: 16,
borderBottomColor: "#ccc",
borderBottomWidth: 1,
}}
onPress={() => {
navigation.navigate("Detail", {
id: news.id,
title: news.title,
content: news.content,
});
}}
>
<Text>{news.title}</Text>
</TouchableOpacity>
))}
</ScrollView>
);
};
export default NewsIndex;
screens/news/detail
tsx
import { Text, View } from "react-native";
type NewsDetailProps = {};
const NewsDetail: React.FC<NewsDetailProps> = ({ route, navigation }) => {
return (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
gap: 16,
}}
>
<Text>{route.params.id}</Text>
<Text>{route.params.title}</Text>
<Text>{route.params.content}</Text>
</View>
);
};
export default NewsDetail;
二、组装路由
在 navigations
目录下,我们新建下图所示的三个 Stack
,并根据步骤完善代码

这里我们新增了几个文件,用来组装我们的路由
RootStackScreen.tsx
根 StackTabScreen.tsx
tabbar 页面stack/NewsStackScreen.tsx
新闻类 Stack,这里新建了一个stacks
文件夹,如果有其他类的路由,也都可以放到这个文件夹里,用于和 Tabbar 的区分
接下来我们给这三个文件补充代码
stacks/NewsStackScreen.tsx
tsx
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import NewsIndex from "@/screens/news";
import NewsDetail from "@/screens/news/detail";
export type NewsParamList = {
Index: undefined;
Detail: {
id: number;
title: string;
content: string;
};
};
const News = createNativeStackNavigator<NewsParamList>();
function NewsScreen() {
return (
<News.Navigator>
<News.Screen name="Index" component={NewsIndex} />
<News.Screen name="Detail" component={NewsDetail} />
</News.Navigator>
);
}
export default NewsScreen;
TabbarScreen.tsx
tsx
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import Home from "@/screens/tabbar/home";
import My from "@/screens/tabbar/my";
import { AntDesign } from "@expo/vector-icons";
export type TabParamList = {
Home: undefined;
My: undefined;
};
const Tab = createBottomTabNavigator<TabParamList>();
function TabScreen() {
return (
<Tab.Navigator>
<Tab.Screen
name="Home"
component={Home}
options={{
tabBarIcon: ({ focused }) => {
return <AntDesign name="home" size={24} color={focused ? "#000" : "#ccc"} />;
},
}}
/>
<Tab.Screen
name="My"
component={My}
options={{
tabBarIcon: ({ focused }) => {
return <AntDesign name="user" size={24} color={focused ? "#000" : "#ccc"} />;
},
}}
/>
</Tab.Navigator>
);
}
export default TabScreen;
RootStackScreen.tsx
tsx
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import TabScreen, { TabParamList } from "./TabScreen";
import NewsScreen, { NewsParamList } from "./stacks/NewsStackScreen";
type StackParamHandler<T> = {
screen: keyof T;
params?: T[keyof T];
};
export type RootStackParamList = {
Tab: StackParamHandler<TabParamList>;
News: StackParamHandler<NewsParamList>;
};
const RootStack = createNativeStackNavigator<RootStackParamList>();
function RootStackScreen() {
return (
<NavigationContainer>
<RootStack.Navigator screenOptions={{ headerShown: false }}>
<RootStack.Screen name="Tab" component={TabScreen} />
<RootStack.Screen name="News" component={NewsScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}
export default RootStackScreen;
createNativeStackNavigator
createNativeStackNavigator
是一个函数,返回一个包含两个属性的对象:Screen 和 Navigator。它们都是用于配置导航器的 React 组件。Navigator 应该包含 Screen 元素作为其子元素,以定义路由的配置。
createNativeStackNavigator
函数还可以支持泛型,使得可以更灵活地指定导航器的参数类型。通过泛型,你可以指定每个屏幕组件的导航参数类型以及导航器的默认参数类型。
在上面的 stacks/NewsStackScreen.tsx
中,我们传入了 NewsParamList
用来规定该 Stack 中拥有那些页面和页面的参数,类型定义如下:
tsx
export type NewsParamList = {
Index: undefined;
Detail: {
id: number;
title: string;
content: string;
};
};
const News = createNativeStackNavigator<NewsParamList>();
这里定义了 news/detail.tsx
中接收 id, title, content 三个参数,那么我们在 news/index.tsx
中跳转时,就需要传入这三个参数
tsx
onPress={() => {
navigation.navigate("Detail", {
id: news.id,
title: news.title,
content: news.content,
});
}}
路由嵌套
路由嵌套的官方文档地址在此 Nesting navigators | React Navigation
在上面的代码中,我们将 TabScreen
和 stacks/NewsStackScreen.tsx
作为子路由嵌套到 RootStackScreen.tsx
内,这里我们处理了一下 RootStackScreen
的类型
tsx
type StackParamHandler<T> = {
screen: keyof T;
params?: T[keyof T];
};
export type RootStackParamList = {
Tab: StackParamHandler<TabParamList>;
News: StackParamHandler<NewsParamList>;
};
const RootStack = createNativeStackNavigator<RootStackParamList>();
实际项目中,嵌套路由的组织会比这里的例子更加复杂
路由跳转
此时我们返回到 tabbar/home.tsx
文件,发现我们编写的
tsx
<Button title="跳转到资讯列表" onPress={() => {
navigation.navigate("News", {
screen: "Index",
});
}}/>
通过给定一个 screen
来确定跳转到对应的嵌套路由的页面。
而打开 news/index.tsx
页面,发现我们在同一个 Stack 中,是不需要通过 screen
这个参数的
tsx
navigation.navigate("Detail", {
news,
});
如果我们在嵌套路由中也不传入 screen
那么就会打开目标栈的第一个页面
具体的页面跳转和传参可以查看官方文档Passing parameters to routes | React Navigation
三、修改 App.tsx
打开 App.tsx
,将代码改成下面这样
tsx
import { StatusBar } from "expo-status-bar";
import RootStackScreen from "./src/navigations/RootStackScreen";
export default function App() {
return (
<>
<StatusBar style="auto" />
<RootStackScreen />
</>
);
}
四、运行
执行命令 yarn start
,运行成功后应该会显示如下,这里录制的 ios,android 可能会有些许差异。如果要抹除系统之间的差异,上方导航栏应该换成自定义导航栏。
类型
打开 news/detail.tsx
文件,可以发现navigation
和 route
提示找不到类型,因为这里我们给的类型声明是一个空的type

接下来我们需要解决类型提示的报错,打开官网Type checking with TypeScript | React Navigation章节
这里介绍了嵌套路由的类型写法,可以使用
CompositeScreenProps
来组织我们的类型
我们首先将 Tabbar 页面和其他 Stack 页面区分开来
Tab 页面类型
- 在
RootStackScreen.tsx
中新增类型声明
tsx
// tab 页面类型
export type CompositeTabScreenProps<T extends keyof TabParamList> = CompositeScreenProps<
BottomTabScreenProps<TabParamList, T>,
NativeStackScreenProps<RootStackParamList>
>;
- 在
tabbar/home.tsx
页面中修改
tsx
// type HomeProps = {};
type HomeProps = CompositeTabScreenProps<"Home">;
此时我们会发现
navigation
的类型提示已经消失,而且 Button onPress
中navigation.navigate
也可以正确进行类型推导了


stacks 页面
刚才解决了 tab 页面的类型,现在我们处理其他 stacks 页面的类型,这个步骤会比 tab 稍微麻烦一点
- 在
RootStackScreen.tsx
中新增类型声明
tsx
// 所有除了 tabbar 之外的页面
export type PageStackUnionType = NewsParamList;
// 其他页面类型
export type CompositePageScreenProps<
T extends PageStackUnionType,
K extends keyof T & string
> = CompositeScreenProps<NativeStackScreenProps<T, K>, NativeStackScreenProps<RootStackParamList>>;
这里我们增加了一个 PageStackUnionType
,用处是将所有除了 Tab 的普通 Stack 放到一起。
这里我们只有一个 NewsParamList
,如果以后我们新增了一组设置相关的页面,比如取名为 SettingsParamList
,则可以这样来修改
tsx
export type PageStackUnionType = NewsParamList | SettingsParamList;
如果有更多的页面加进来,只需要继续按照 SettingsParamList
的写法来扩展 PageStackUnionType
就可以。
- 在
news/detail.tsx
中修改
tsx
// type NewsDetailProps = {};
type NewsDetailProps = CompositePageScreenProps<NewsParamList, "Detail">;
此时发现我们的 route
也可以通过类型来正确推导了

接下来大家在根据上面的例子将剩余的页面也修改掉,那么路由的类型章节就结束了,最后附上修改完类型的 RootStackScreen.tsx
完整代码
tsx
import { CompositeScreenProps, NavigationContainer } from "@react-navigation/native";
import { NativeStackScreenProps, createNativeStackNavigator } from "@react-navigation/native-stack";
import TabScreen, { TabParamList } from "./TabScreen";
import NewsScreen, { NewsParamList } from "./stacks/NewsStackScreen";
import { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
// tab 页面类型
export type CompositeTabScreenProps<T extends keyof TabParamList> = CompositeScreenProps<
BottomTabScreenProps<TabParamList, T>,
NativeStackScreenProps<RootStackParamList>
>;
// 所有除了 tabbar 之外的页面
export type PageStackUnionType = NewsParamList;
// 其他页面类型
export type CompositePageScreenProps<
T extends PageStackUnionType,
K extends keyof T & string
> = CompositeScreenProps<NativeStackScreenProps<T, K>, NativeStackScreenProps<RootStackParamList>>;
type StackParamHandler<T> = {
screen: keyof T;
params?: T[keyof T];
};
export type RootStackParamList = {
Tab: StackParamHandler<TabParamList>;
News: StackParamHandler<NewsParamList>;
};
const RootStack = createNativeStackNavigator<RootStackParamList>();
function RootStackScreen() {
return (
<NavigationContainer>
<RootStack.Navigator screenOptions={{ headerShown: false }}>
<RootStack.Screen name="Tab" component={TabScreen} />
<RootStack.Screen name="News" component={NewsScreen} />
</RootStack.Navigator>
</NavigationContainer>
);
}
export default RootStackScreen;