前言
基于上篇文章《React Native DApp 开发全栈实战·从 0 到 1 系列(开篇)》,本文聚焦 React Native 路由方案:从导航架构选型到实战落地,带你一次配好、随处复用;
项目结构目录如下
csharp
RnDApp/
├── android/ # Android 原生工程(Expo Prebuild 后生成)
├── api/ # 后端接口封装层
├── app/ # Expo Router 路由目录
│ ├── (tabs)/ # 底部 Tab 路由组
│ │ ├── discover/ # /discover
│ │ ├── home/ # /home
│ │ ├── my/ # /my
│ │ ├── swap/ # /swap
│ │ └── trade/ # /trade
│ │ └── layout.tsx # Tab 布局
│ ├── profile/ # 独立路由组
│ │ ├── layout.tsx
│ │ └── +not-found.tsx
│ ├── createAccount.tsx # /createAccount
│ ├── createWallet.tsx # /createWallet
│ ├── index.tsx # 首页 /
│ ├── login.tsx # /login
│ └── register.tsx # /register
├── assets/ # 图片、字体、音视频等静态资源
├── components/ # 公共业务组件
├── constants/ # 枚举、常量、主题配置
├── hooks/ # 自定义 React Hooks
├── node_modules/ # 依赖包
├── scripts/ # 构建、自动化脚本
├── stores/ # 全局状态管理(Zustand / Redux)
├── .gitignore
├── app.json # Expo 项目配置
├── babel.config.js
├── global.css # Babel 配置(NativeWind 等)
├── metro.config.js
├── package.json
├── tailwind.config.js
└── tsconfig.json
项目说明
- 样式:Nativewind(Tailwind CSS 语法)
- 状态:TanStack Query(服务端)+ Zustand(客户端)
- 请求:Axios
- 路由:Expo Router
expo-router(文件路由)
说明
:文件即路由,括号文件夹不生成路径,_layout.tsx
负责导航配置
分类描述
1. 文件配置[/app/_layout.tsx]
javascript
import { useColorScheme } from '@/hooks/useColorScheme';
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import 'react-native-reanimated';
import "../global.css";
const queryClient = new QueryClient();
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});
if (!loaded) {
// Async font loading only occurs in development.
return null;
}
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<QueryClientProvider client={queryClient}>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
<StatusBar style="auto" />
</QueryClientProvider>
</ThemeProvider>
);
}
说明:配置了tanstack/react-query和tailwind以及路由配置:包含底部导航和入口文件以及未匹配路由页面
2. 底部导航配置[/app/tabs/_layout.tsx] & [/app/tabs/home/_layout.tsx]
- [/app/tabs/_layout.tsx]
php
import { HapticTab } from '@/components/HapticTab';
import { IconSymbol } from '@/components/ui/IconSymbol';
import TabBarBackground from '@/components/ui/TabBarBackground';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import FontAwesome6 from '@expo/vector-icons/FontAwesome6';
import Ionicons from '@expo/vector-icons/Ionicons';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { CommonActions } from '@react-navigation/native';
import { Tabs } from 'expo-router';
import React from 'react';
import { Platform } from 'react-native';
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<>
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarButton: HapticTab,
tabBarBackground: TabBarBackground,
tabBarStyle: Platform.select({
ios: {
// Use a transparent background on iOS to show the blur effect
position: 'absolute',
},
default: {},
}),
}}>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarLabel: () => null,
tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
}}
listeners={({ navigation }) => ({
tabPress: (e) => {
e.preventDefault(); // 阻止默认跳转
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'home', // 对应 my/index
state: {
routes: [{ name: 'index' }],
index: 0,
},
},
],
})
);
},
})}
/>
<Tabs.Screen
name="trade"
options={{
// title: 'Trade',
tabBarLabel: () => null,
tabBarIcon: ({ color, focused }) => (
<FontAwesome6 name="btc" size={24} color={color} />
),
}}
listeners={({ navigation }) => ({
tabPress: (e) => {
e.preventDefault(); // 阻止默认跳转
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'trade', // 对应 my/index
state: {
routes: [{ name: 'index' }],
index: 0,
},
},
],
})
);
},
})}
/>
<Tabs.Screen
name="swap"
options={{
// title: 'Trade',
tabBarLabel: () => null,
tabBarIcon: ({ color, focused }) => (
<MaterialIcons name="swap-horizontal-circle" size={24} color={color} />
),
}}
listeners={({ navigation }) => ({
tabPress: (e) => {
e.preventDefault(); // 阻止默认跳转
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'swap', // 对应 my/index
state: {
routes: [{ name: 'index' }],
index: 0,
},
},
],
})
);
},
})}
/>
<Tabs.Screen
name="discover"
options={{
tabBarLabel: () => null,
// title: 'My',
tabBarIcon: ({ color, focused }) => (
<Ionicons name="compass" size={24} color={color} />
),
}}
listeners={({ navigation }) => ({
tabPress: (e) => {
e.preventDefault(); // 阻止默认跳转
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'discover', // 对应 my/index
state: {
routes: [{ name: 'index' }],
index: 0,
},
},
],
})
);
},
})}
/>
<Tabs.Screen
name="my"
options={{
tabBarLabel: () => null,
// title: 'My',
tabBarIcon: ({ color, focused }) => (
<FontAwesome6 name="user-large" size={24} color={color} />
),
}}
listeners={({ navigation }) => ({
tabPress: (e) => {
e.preventDefault(); // 阻止默认跳转
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [
{
name: 'my', // 对应 my/index
state: {
routes: [{ name: 'index' }],
index: 0,
},
},
],
})
);
},
})}
/>
</Tabs>
</>);
}
说明:listeners监听事件解决底部导航跳转默认页面(index)options主要配置导航的设置包含icon和文字
- [/app/tabs/home/_layout.tsx]
javascript
// app/(tabs)/home/_layout.tsx
import { Stack } from 'expo-router';
export default function DiscoverStack() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
</Stack>
);
}
说明:底部导航要配合使用,主要解决双导航问题
3. 文件夹不含导航配置[/app/xxx/_layout.tsx]
javascript
同上[/app/tabs/home/_layout.tsx]
汇总速查
分类 | 路径示例 | 对应路由 | 导航行为 |
---|---|---|---|
独立页面 | /app/index.tsx |
/ |
无父级导航,直接渲染 |
独立页面 | /app/login.tsx |
/login |
同上 |
底部导航 | /app/(tabs)/home.tsx |
/home |
自动嵌套 Tab;由 /app/(tabs)/_layout.tsx 统一配置 |
底部导航 | /app/(tabs)/swap.tsx |
/swap |
同上 |
分组文件夹(无导航) | /app/profile/settings.tsx |
/profile/settings |
仅做路径分组,不额外生成导航层级 |
效果图
总结
至此,导航系统配置已全部完成,以及项目的页面效果。