mobile-navigation-react-navigation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Navigation Patterns

React Navigation 模式

Quick Guide: Use the static API for simpler TypeScript inference and automatic deep linking config. Use the dynamic API when you need runtime-dynamic screen lists. Always declare a global
RootParamList
for type-safe
useNavigation
everywhere. Use
createNativeStackNavigator
(not the JS stack) for production performance. Auth flows use conditional screen rendering via the
if
callback (static) or conditional JSX (dynamic). Deep linking config lives per-screen in the static API -- no separate config object needed.

<critical_requirements>
快速指南: 静态API适用于更简洁的TypeScript类型推断和自动深度链接配置。当你需要运行时动态屏幕列表时,使用动态API。务必声明全局
RootParamList
,让所有地方的
useNavigation
都具备类型安全性。生产环境中使用
createNativeStackNavigator
(而非JS栈)以保证性能。认证流程通过
if
回调(静态API)或条件JSX(动态API)实现条件屏幕渲染。静态API中,深度链接配置在每个屏幕中定义——无需单独的配置对象。

<critical_requirements>

CRITICAL: Before Using This Skill

重要须知:使用此技能前

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type
, named constants)
(You MUST declare a global
ReactNavigation.RootParamList
interface so
useNavigation
is type-safe without manual annotation)
(You MUST use
createNativeStackNavigator
for production apps -- the JS stack (
@react-navigation/stack
) is significantly slower and only needed for highly custom transitions)
(You MUST use
popTo()
to navigate back to a previous screen in the stack --
navigate()
in v7 no longer pops back to existing screens)
(You MUST wrap
useFocusEffect
callbacks in
useCallback
-- without it, the effect runs on every render, not just focus changes)
(You MUST NOT use
navigation.navigate('NestedScreen')
to reach screens in child navigators -- v7 removed implicit nested navigation; use explicit parent targeting)
</critical_requirements>

Auto-detection: React Navigation, @react-navigation, createNativeStackNavigator, createBottomTabNavigator, createDrawerNavigator, createStaticNavigation, NavigationContainer, useNavigation, useRoute, useFocusEffect, usePreventRemove, StaticParamList, StaticScreenProps, NativeStackNavigationProp, CompositeNavigationProp, NavigatorScreenParams, deep linking, linking config, headerSearchBarOptions, headerLargeTitle, popTo, preload
When to use:
  • Setting up navigation structure (stack, tab, drawer) in a React Native app
  • Choosing between static API and dynamic API for navigator configuration
  • Adding type-safe navigation with TypeScript (param lists, typed hooks)
  • Configuring deep linking (URL prefixes, path params, universal links)
  • Implementing authentication flows with conditional screen rendering
  • Customizing headers (large titles, search bars, custom buttons)
  • Preloading screens for perceived performance
  • Preventing back navigation for unsaved changes
When NOT to use:
  • File-based routing with a managed workflow (uses its own router built on React Navigation)
  • Web-only React apps (use a web router)
  • Simple single-screen apps with no navigation
Key patterns covered:
  • Static API vs dynamic API: when to use each
  • Global
    RootParamList
    declaration for type-safe hooks everywhere
  • Native stack vs JS stack performance trade-offs
  • Auth flow with conditional screens (static
    if
    callback or dynamic JSX)
  • Deep linking configuration (per-screen in static,
    linking
    prop in dynamic)
  • Screen preloading with
    navigation.preload()
  • useFocusEffect
    for screen lifecycle management
  • usePreventRemove
    for unsaved changes guards
  • Header customization: large titles, search bars, form sheets
Detailed Resources:
  • examples/core.md - Static API setup, dynamic API setup, type-safe navigation, global RootParamList
  • examples/patterns.md - Auth flows, deep linking, modals, tab navigator with nested stacks
  • examples/advanced.md - Screen preloading, state persistence, usePreventRemove, useFocusEffect, header customization
  • reference.md - Decision frameworks, screen options cheat sheet, v6-to-v7 migration

<philosophy>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入顺序、
import type
、命名常量)
(你必须声明全局
ReactNavigation.RootParamList
接口,这样
useNavigation
无需手动注解就具备类型安全性)
(生产应用必须使用
createNativeStackNavigator
——JS栈(
@react-navigation/stack
)速度明显更慢,仅在需要高度自定义转场时使用)
(返回栈中之前的屏幕必须使用
popTo()
——v7中
navigate()
不再返回已有屏幕)
useFocusEffect
回调必须包裹在
useCallback
中——否则效果会在每次渲染时运行,而非仅在焦点变化时运行)
(禁止使用
navigation.navigate('NestedScreen')
访问子导航器中的屏幕——v7移除了隐式子导航;需显式指定父级目标)
</critical_requirements>

自动检测: React Navigation, @react-navigation, createNativeStackNavigator, createBottomTabNavigator, createDrawerNavigator, createStaticNavigation, NavigationContainer, useNavigation, useRoute, useFocusEffect, usePreventRemove, StaticParamList, StaticScreenProps, NativeStackNavigationProp, CompositeNavigationProp, NavigatorScreenParams, deep linking, linking config, headerSearchBarOptions, headerLargeTitle, popTo, preload
适用场景:
  • 在React Native应用中搭建导航结构(栈、标签、抽屉)
  • 为导航器配置选择静态API还是动态API
  • 通过TypeScript添加类型安全导航(参数列表、类型化钩子)
  • 配置深度链接(URL前缀、路径参数、通用链接)
  • 通过条件屏幕渲染实现认证流程
  • 自定义头部(大标题、搜索栏、自定义按钮)
  • 预加载屏幕以提升感知性能
  • 防止未保存更改时的返回导航
不适用场景:
  • 使用托管工作流的基于文件的路由(其内置了基于React Navigation的路由)
  • 仅Web端的React应用(使用Web路由)
  • 无导航需求的简单单屏应用
涵盖的核心模式:
  • 静态API vs 动态API:各自适用场景
  • 全局
    RootParamList
    声明,让所有钩子都具备类型安全性
  • Native栈与JS栈的性能权衡
  • 带条件屏幕的认证流程(静态
    if
    回调或动态JSX)
  • 深度链接配置(静态API中按屏幕配置,动态API中使用
    linking
    属性)
  • 使用
    navigation.preload()
    预加载屏幕
  • 使用
    useFocusEffect
    管理屏幕生命周期
  • 使用
    usePreventRemove
    防止未保存更改
  • 头部自定义:大标题、搜索栏、表单页
详细资源:
  • examples/core.md - 静态API设置、动态API设置、类型安全导航、全局RootParamList
  • examples/patterns.md - 认证流程、深度链接、模态框、带嵌套栈的标签导航器
  • examples/advanced.md - 屏幕预加载、状态持久化、usePreventRemove、useFocusEffect、头部自定义
  • reference.md - 决策框架、屏幕选项速查表、v6到v7迁移指南

<philosophy>

Philosophy

设计理念

React Navigation provides routing and navigation for React Native apps. The key decision in v7 is static vs dynamic API:
  • Static API -- object-based configuration. Simpler TypeScript (types inferred from config), automatic deep linking path generation, less boilerplate. Use for most apps.
  • Dynamic API -- component-based configuration (
    <Stack.Navigator>
    /
    <Stack.Screen>
    ). Required when screen lists change at runtime or you need full programmatic control over navigator props. More verbose but more flexible.
Both APIs produce the same navigation behavior -- the difference is configuration ergonomics.
Core principles:
  1. Native stack by default --
    createNativeStackNavigator
    uses platform navigation primitives (UINavigationController/Fragment) for smoother transitions and lower memory. The JS stack (
    @react-navigation/stack
    ) only when you need custom transition animations not available natively.
  2. Type safety from the root -- Declare
    ReactNavigation.RootParamList
    globally so every
    useNavigation()
    call is type-checked without manual generics.
  3. Deep linking as first-class -- Configure linking per-screen (static API) or in a centralized config (dynamic API). Prefixes handle custom schemes and universal links.
  4. Screen lifecycle via focus -- Screens in a stack remain mounted when covered. Use
    useFocusEffect
    (not
    useEffect
    ) for work that should pause when the screen loses focus.
v7 behavioral changes from v6:
  • navigate()
    no longer pops back to existing screens -- use
    popTo()
    instead
  • Implicit nested navigator navigation removed -- must target parent screen explicitly
  • headerBackTitleVisible
    replaced with
    headerBackButtonDisplayMode
  • Navigation state is frozen in dev mode (mutations throw)
  • Theme objects now require a
    fonts
    property
</philosophy>
<patterns>
React Navigation为React Native应用提供路由和导航功能。v7中的核心决策是静态API vs 动态API
  • 静态API——基于对象的配置。TypeScript使用更简洁(类型从配置中推断)、自动生成深度链接路径、样板代码更少。适用于大多数应用。
  • 动态API——基于组件的配置(
    <Stack.Navigator>
    /
    <Stack.Screen>
    )。当屏幕列表在运行时变化,或需要对导航器属性进行完全程序化控制时使用。更冗长但更灵活。
两种API产生的导航行为相同——区别在于配置的易用性。
核心原则:
  1. 默认使用Native栈——
    createNativeStackNavigator
    使用平台导航原语(UINavigationController/Fragment),实现更流畅的转场和更低的内存占用。仅当需要原生栈不支持的自定义转场动画时,才使用JS栈(
    @react-navigation/stack
    )。
  2. 从根节点保证类型安全——全局声明
    ReactNavigation.RootParamList
    ,这样每个
    useNavigation()
    调用都能进行类型检查,无需手动泛型。
  3. 深度链接作为一等公民——在静态API中按屏幕配置链接,或在动态API中使用集中式配置。前缀处理自定义协议和通用链接。
  4. 通过焦点管理屏幕生命周期——栈中的屏幕被覆盖时仍保持挂载状态。使用
    useFocusEffect
    (而非
    useEffect
    )处理屏幕失去焦点时应暂停的操作。
v7相对v6的行为变化:
  • navigate()
    不再返回已有屏幕——改用
    popTo()
  • 移除了隐式子导航器导航——必须显式指定父级屏幕
  • headerBackTitleVisible
    替换为
    headerBackButtonDisplayMode
  • 开发模式下导航状态被冻结(修改会抛出错误)
  • 主题对象现在需要
    fonts
    属性
</philosophy>
<patterns>

Core Patterns

核心模式

Pattern 1: Static API Setup

模式1:静态API设置

The static API uses object configuration for simpler TypeScript and automatic deep linking.
typescript
import { createStaticNavigation } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import type { StaticParamList } from "@react-navigation/native";

const RootStack = createNativeStackNavigator({
  initialRouteName: "Home",
  screenOptions: { headerShown: true },
  screens: {
    Home: HomeScreen,
    Profile: {
      screen: ProfileScreen,
      linking: "profile/:userId",
    },
  },
});

const Navigation = createStaticNavigation(RootStack);

// Declare global types -- makes useNavigation() type-safe everywhere
type RootStackParamList = StaticParamList<typeof RootStack>;
declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

export function App() {
  return <Navigation />;
}
Why good: types inferred from config (no manual
ParamList
), deep linking paths defined per-screen, less boilerplate than dynamic API
See examples/core.md for complete static API setup with groups and conditional screens.

静态API使用对象配置,实现更简洁的TypeScript支持和自动深度链接。
typescript
import { createStaticNavigation } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import type { StaticParamList } from "@react-navigation/native";

const RootStack = createNativeStackNavigator({
  initialRouteName: "Home",
  screenOptions: { headerShown: true },
  screens: {
    Home: HomeScreen,
    Profile: {
      screen: ProfileScreen,
      linking: "profile/:userId",
    },
  },
});

const Navigation = createStaticNavigation(RootStack);

// 声明全局类型——让所有地方的useNavigation()都具备类型安全性
type RootStackParamList = StaticParamList<typeof RootStack>;
declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

export function App() {
  return <Navigation />;
}
优势: 类型从配置中推断(无需手动
ParamList
)、深度链接路径按屏幕定义、比动态API更少的样板代码
查看examples/core.md了解包含分组和条件屏幕的完整静态API设置。

Pattern 2: Dynamic API Setup

模式2:动态API设置

The dynamic API uses JSX components. Use when screen lists are runtime-dynamic.
typescript
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
};

// Must declare globally for type-safe useNavigation()
declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

const Stack = createNativeStackNavigator<RootStackParamList>();

export function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
Why good: familiar JSX pattern, supports runtime-dynamic screen lists, manual param list gives explicit control
See examples/core.md for dynamic API with typed hooks and nested navigators.

动态API使用JSX组件。适用于屏幕列表在运行时动态变化的场景。
typescript
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
};

// 必须全局声明以实现类型安全的useNavigation()
declare global {
  namespace ReactNavigation {
    interface RootParamList extends RootStackParamList {}
  }
}

const Stack = createNativeStackNavigator<RootStackParamList>();

export function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}
优势: 熟悉的JSX模式、支持运行时动态屏幕列表、手动参数列表提供显式控制
查看examples/core.md了解带类型化钩子和嵌套导航器的动态API设置。

Pattern 3: Type-Safe Navigation Hooks

模式3:类型安全导航钩子

Declare
RootParamList
globally once, then
useNavigation()
and
useRoute()
are type-safe everywhere without manual generics.
typescript
// In any screen component -- no generic needed
function HomeScreen() {
  const navigation = useNavigation();

  // Type-checked: "Profile" must exist, params must match
  navigation.navigate("Profile", { userId: "123" });

  // Type error: "Nonexistent" is not in RootParamList
  navigation.navigate("Nonexistent"); // compile error
}
For nested navigators, use
CompositeScreenProps
or
NavigatorScreenParams
to propagate types. With the static API, use
StaticScreenProps
for screen component props.
See examples/core.md for composite types and
StaticScreenProps
.

全局声明一次
RootParamList
,之后
useNavigation()
useRoute()
在所有地方都具备类型安全性,无需手动泛型。
typescript
// 在任何屏幕组件中——无需泛型
function HomeScreen() {
  const navigation = useNavigation();

  // 类型检查:"Profile"必须存在,参数必须匹配
  navigation.navigate("Profile", { userId: "123" });

  // 类型错误:"Nonexistent"不在RootParamList中
  navigation.navigate("Nonexistent"); // 编译错误
}
对于嵌套导航器,使用
CompositeScreenProps
NavigatorScreenParams
传递类型。在静态API中,使用
StaticScreenProps
作为屏幕组件的属性。
查看examples/core.md了解复合类型和
StaticScreenProps
的使用。

Pattern 4: Authentication Flow

模式4:认证流程

Conditionally render auth or main screens. React Navigation animates the transition automatically.
typescript
// Static API: use the `if` callback on groups
const useIsAuthenticated = () => {
  const { isAuthenticated } = useContext(AuthContext);
  return isAuthenticated;
};

const useIsGuest = () => !useIsAuthenticated();

const RootStack = createNativeStackNavigator({
  screens: {},
  groups: {
    Auth: {
      if: useIsGuest,
      screenOptions: { headerShown: false },
      screens: { Login: LoginScreen, Register: RegisterScreen },
    },
    Main: {
      if: useIsAuthenticated,
      screens: { Home: HomeScreen, Profile: ProfileScreen },
    },
  },
});
Why good:
if
callbacks cleanly separate auth/main screens, React Navigation handles transition animation, no manual state-based conditional rendering needed
See examples/patterns.md for both static and dynamic auth flow implementations.

有条件地渲染认证或主屏幕。React Navigation会自动处理转场动画。
typescript
// 静态API:在分组上使用`if`回调
const useIsAuthenticated = () => {
  const { isAuthenticated } = useContext(AuthContext);
  return isAuthenticated;
};

const useIsGuest = () => !useIsAuthenticated();

const RootStack = createNativeStackNavigator({
  screens: {},
  groups: {
    Auth: {
      if: useIsGuest,
      screenOptions: { headerShown: false },
      screens: { Login: LoginScreen, Register: RegisterScreen },
    },
    Main: {
      if: useIsAuthenticated,
      screens: { Home: HomeScreen, Profile: ProfileScreen },
    },
  },
});
优势:
if
回调清晰分离认证/主屏幕、React Navigation处理转场动画、无需手动基于状态的条件渲染
查看examples/patterns.md了解静态和动态两种认证流程实现。

Pattern 5: Deep Linking

模式5:深度链接

Static API: define
linking
per-screen. Dynamic API: pass a
linking
config to
NavigationContainer
.
typescript
// Static API -- linking defined inline per screen
const RootStack = createNativeStackNavigator({
  screens: {
    Home: { screen: HomeScreen, linking: "" },
    Profile: {
      screen: ProfileScreen,
      linking: {
        path: "user/:userId",
        parse: { userId: (id: string) => id.replace(/^@/, "") },
        stringify: { userId: (id: string) => `@${id}` },
      },
    },
  },
});

const Navigation = createStaticNavigation(RootStack);

export function App() {
  return (
    <Navigation
      linking={{ prefixes: ["myapp://", "https://myapp.com"] }}
    />
  );
}
Why good: linking config co-located with screen definition, parse/stringify handle URL encoding, prefixes handle both custom scheme and universal links
See examples/patterns.md for dynamic API linking, custom URL handlers, and platform-specific setup.

静态API:按屏幕定义
linking
。动态API:向
NavigationContainer
传递
linking
配置。
typescript
// 静态API——链接配置内联在每个屏幕中
const RootStack = createNativeStackNavigator({
  screens: {
    Home: { screen: HomeScreen, linking: "" },
    Profile: {
      screen: ProfileScreen,
      linking: {
        path: "user/:userId",
        parse: { userId: (id: string) => id.replace(/^@/, "") },
        stringify: { userId: (id: string) => `@${id}` },
      },
    },
  },
});

const Navigation = createStaticNavigation(RootStack);

export function App() {
  return (
    <Navigation
      linking={{ prefixes: ["myapp://", "https://myapp.com"] }}
    />
  );
}
优势: 链接配置与屏幕定义共存、parse/stringify处理URL编码、前缀同时处理自定义协议和通用链接
查看examples/patterns.md了解动态API链接、自定义URL处理器和平台特定设置。

Pattern 6: Native Stack vs JS Stack

模式6:Native栈 vs JS栈

Which stack navigator?
|-- Need custom JS-driven transition animations? --> @react-navigation/stack (JS)
|-- Everything else --> @react-navigation/native-stack (NATIVE)
FeatureNative StackJS Stack
PerformanceNative animations, lower memoryJS-driven, higher overhead
TransitionsPlatform defaults + limited customFully customizable
Large titles (iOS)Supported nativelyNot available
Search bar (iOS)headerSearchBarOptionsMust build custom
Form sheetspresentation: "formSheet"Not available
Gesture handlingNative, smoothJS-driven
Default to native stack. Only use JS stack when you need transition animations that native stack cannot provide.

选择哪种栈导航器?
|-- 需要自定义JS驱动的转场动画? --> @react-navigation/stack(JS栈)
|-- 其他场景 --> @react-navigation/native-stack(Native栈)
特性Native栈JS栈
性能原生动画,更低内存占用JS驱动,更高开销
转场平台默认 + 有限自定义完全可自定义
iOS大标题原生支持不支持
iOS搜索栏headerSearchBarOptions必须自定义
表单页presentation: "formSheet"不支持
手势处理原生,流畅JS驱动
默认使用Native栈。仅当需要Native栈无法提供的转场动画时,才使用JS栈。

Pattern 7: useFocusEffect for Screen Lifecycle

模式7:使用useFocusEffect管理屏幕生命周期

Screens in a stack remain mounted when a new screen is pushed. Use
useFocusEffect
to run effects only when the screen is focused.
typescript
import { useCallback } from "react";
import { useFocusEffect } from "@react-navigation/native";

function ChatScreen({ roomId }: { roomId: string }) {
  useFocusEffect(
    useCallback(() => {
      const ws = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
      // Cleanup runs when screen loses focus
      return () => ws.close();
    }, [roomId]),
  );
}
Gotcha: The callback MUST be wrapped in
useCallback
. Without it, the effect re-runs on every render, not just focus changes.
See examples/advanced.md for polling, analytics tracking, and resource cleanup patterns.

当新屏幕被推入栈时,栈中的原有屏幕仍保持挂载状态。使用
useFocusEffect
仅在屏幕获得焦点时运行效果。
typescript
import { useCallback } from "react";
import { useFocusEffect } from "@react-navigation/native";

function ChatScreen({ roomId }: { roomId: string }) {
  useFocusEffect(
    useCallback(() => {
      const ws = new WebSocket(`wss://chat.example.com/rooms/${roomId}`);
      // 屏幕失去焦点时执行清理
      return () => ws.close();
    }, [roomId]),
  );
}
注意事项: 回调必须包裹在
useCallback
中。否则效果会在每次渲染时运行,而非仅在焦点变化时运行。
查看examples/advanced.md了解轮询、分析跟踪和资源清理模式。

Pattern 8: Screen Preloading

模式8:屏幕预加载

Preload heavy screens before the user navigates to them. The screen is rendered off-screen with all hooks running.
typescript
function ProductList() {
  const navigation = useNavigation();

  const handleLongPress = (productId: string) => {
    navigation.preload("ProductDetail", { productId });
  };
  // Later: navigation.navigate("ProductDetail", { productId }) is instant
}
Limitations: Preloaded screens cannot dispatch navigation actions, update options, or listen to events until actually navigated to.

在用户导航到重型屏幕之前预加载它们。屏幕会在离屏状态下渲染,所有钩子都会运行。
typescript
function ProductList() {
  const navigation = useNavigation();

  const handleLongPress = (productId: string) => {
    navigation.preload("ProductDetail", { productId });
  };
  // 后续:navigation.navigate("ProductDetail", { productId })会瞬间完成
}
限制: 预加载的屏幕在实际导航到之前,无法分发导航操作、更新选项或监听事件。

Pattern 9: Header Customization

模式9:头部自定义

Native stack supports platform-native header features: large titles, search bars, and form sheets.
typescript
<Stack.Screen
  name="Settings"
  component={SettingsScreen}
  options={{
    headerLargeTitleEnabled: true,
    headerLargeStyle: { backgroundColor: "#f5f5f5" },
    headerSearchBarOptions: {
      placeholder: "Search settings...",
      onChangeText: (e) => handleSearch(e.nativeEvent.text),
      hideWhenScrolling: true,
    },
  }}
/>
Gotcha: Custom
header
functions disable ALL native header features (large title, search bar, blur effects). Use
headerLeft
/
headerRight
to add custom elements while keeping native behavior.
See examples/advanced.md for form sheets, custom header items, and search bar integration.
</patterns>
<decision_framework>
Native栈支持平台原生头部特性:大标题、搜索栏和表单页。
typescript
<Stack.Screen
  name="Settings"
  component={SettingsScreen}
  options={{
    headerLargeTitleEnabled: true,
    headerLargeStyle: { backgroundColor: "#f5f5f5" },
    headerSearchBarOptions: {
      placeholder: "搜索设置...",
      onChangeText: (e) => handleSearch(e.nativeEvent.text),
      hideWhenScrolling: true,
    },
  }}
/>
注意事项: 自定义
header
函数会禁用所有原生头部特性(大标题、搜索栏、模糊效果)。使用
headerLeft
/
headerRight
添加自定义元素,同时保留原生行为。
查看examples/advanced.md了解表单页、自定义头部项和搜索栏集成。
</patterns>
<decision_framework>

Decision Framework

决策框架

Static vs Dynamic API

静态API vs 动态API

Starting a new navigation setup?
|-- Can all screens be defined at build time?
|   |-- YES --> Static API (simpler TS, auto deep linking)
|   +-- NO  --> Dynamic API (runtime screen lists)
|
|-- Migrating incrementally from v6?
|   +-- YES --> Dynamic API at root, static for new navigators
|       (use getComponent() and createPathConfigForStaticNavigation)
|
|-- Need to wrap navigator with providers (e.g. context)?
|   +-- Use static API with .with() method
开始新的导航设置?
|-- 所有屏幕都能在构建时定义吗?
|   |-- 是 --> 静态API(更简洁的TS支持、自动深度链接)
|   +-- 否 --> 动态API(运行时屏幕列表)
|
|-- 从v6增量迁移?
|   +-- 是 --> 根节点使用动态API,新导航器使用静态API
|      (使用getComponent()和createPathConfigForStaticNavigation)
|
|-- 需要用提供者包裹导航器(如context)?
|   +-- 使用静态API的.with()方法

Navigator Type

导航器类型

What navigation pattern?
|-- Linear flow (onboarding, checkout) --> Stack Navigator
|-- Main app sections with persistent bar --> Bottom Tab Navigator
|-- Side menu / settings panel --> Drawer Navigator
|-- Modal overlays --> Stack with presentation: "modal"
|-- Bottom sheets --> Stack with presentation: "formSheet"
|-- Combination --> Nest navigators (tabs inside stack, stacks inside tabs)
选择哪种导航模式?
|-- 线性流程(引导页、结账流程) --> 栈导航器
|-- 带持久化栏的主应用板块 --> 底部标签导航器
|-- 侧边菜单/设置面板 --> 抽屉导航器
|-- 模态覆盖层 --> 栈导航器,设置presentation: "modal"
|-- 底部表单 --> 栈导航器,设置presentation: "formSheet"
|-- 组合模式 --> 嵌套导航器(栈内嵌套标签,标签内嵌套栈)

Navigation Method

导航方式

How to move between screens?
|-- Push new screen forward --> navigation.navigate("Screen", params)
|-- Go back to specific screen --> navigation.popTo("Screen", params)
|-- Go back one screen --> navigation.goBack()
|-- Replace current screen --> navigation.replace("Screen", params)
|-- Reset entire stack --> navigation.reset({ routes: [...] })
|-- Navigate to nested screen --> navigation.navigate("Parent", { screen: "Child" })
</decision_framework>

<red_flags>
如何在屏幕间跳转?
|-- 向前推入新屏幕 --> navigation.navigate("Screen", params)
|-- 返回指定屏幕 --> navigation.popTo("Screen", params)
|-- 返回上一屏幕 --> navigation.goBack()
|-- 替换当前屏幕 --> navigation.replace("Screen", params)
|-- 重置整个栈 --> navigation.reset({ routes: [...] })
|-- 导航到嵌套屏幕 --> navigation.navigate("Parent", { screen: "Child" })
</decision_framework>

<red_flags>

RED FLAGS

注意事项

High Priority Issues:
  • Using
    navigate()
    to go back to a previous screen -- v7 changed behavior;
    navigate()
    stays on current screen if target exists. Use
    popTo()
    instead.
  • Using
    navigation.navigate("NestedScreen")
    to reach child navigator screens -- removed in v7. Must use
    navigate("ParentScreen", { screen: "NestedScreen" })
    .
  • Using JS stack (
    @react-navigation/stack
    ) for production without a specific need for custom transitions -- native stack is significantly more performant.
  • Missing global
    RootParamList
    declaration -- every
    useNavigation()
    call is untyped, losing the primary benefit of TypeScript with React Navigation.
  • Using a custom
    header
    function and expecting native features (large title, search bar, blur) -- custom headers disable all native header functionality.
Medium Priority Issues:
  • Inline component functions in
    <Stack.Screen component={() => <MyScreen />} />
    -- creates a new component on every render, causing unmount/remount. Always pass a reference.
  • Not using
    useFocusEffect
    for screen-specific side effects --
    useEffect
    runs even when the screen is covered by another screen in the stack.
  • Mutating navigation state directly (caught in dev mode in v7, silent corruption in prod).
  • Missing
    fonts
    property in custom theme -- required in v7, crashes without it.
Gotchas & Edge Cases:
  • useFocusEffect
    callback must be wrapped in
    useCallback
    -- without it, the effect fires on every render, not just focus changes
  • usePreventRemove
    only fires for navigation state removal (back, pop, reset) -- it does NOT fire when the screen is merely unfocused (push, tab switch)
  • Preloaded screens cannot dispatch navigation actions or call
    navigation.setOptions()
    until actually navigated to
  • Screen
    options
    can be an object or a function receiving
    { route, navigation }
    -- use the function form when options depend on route params
  • headerSearchBarOptions
    requires
    contentInsetAdjustmentBehavior="automatic"
    on your ScrollView/FlatList for proper layout
  • headerBackButtonDisplayMode
    replaced
    headerBackTitleVisible
    in v7 -- values are "default", "generic", or "minimal"
  • unmountOnBlur
    removed from tabs/drawer in v7 -- use
    popToTopOnBlur: true
    or the
    useIsFocused
    pattern instead
  • Navigation state frozen in dev mode -- if you were mutating state directly, you'll get runtime errors in v7 dev builds
  • Android requires
    RNScreensFragmentFactory
    setup in
    MainActivity
    -- without it, View state is lost during Activity restarts
  • The
    Link
    component changed from path-based to screen-based:
    <Link screen="Profile" params={{ userId }}>
    not
    <Link to="/profile/123">
</red_flags>

<critical_reminders>
高优先级问题:
  • 使用
    navigate()
    返回之前的屏幕——v7改变了行为;如果目标屏幕已存在,
    navigate()
    会停留在当前屏幕。改用
    popTo()
  • 使用
    navigation.navigate("NestedScreen")
    访问子导航器中的屏幕——v7已移除此功能。必须使用
    navigate("ParentScreen", { screen: "NestedScreen" })
  • 在生产环境中使用JS栈(
    @react-navigation/stack
    )且无自定义转场的特定需求——Native栈性能明显更优。
  • 缺少全局
    RootParamList
    声明——每个
    useNavigation()
    调用都无类型检查,失去了React Navigation结合TypeScript的主要优势。
  • 使用自定义
    header
    函数却期望原生特性(大标题、搜索栏、模糊效果)——自定义头部会禁用所有原生头部功能。
中优先级问题:
  • <Stack.Screen component={() => <MyScreen />} />
    中使用内联组件函数——每次渲染都会创建新组件,导致卸载/重新挂载。始终传递引用。
  • 未使用
    useFocusEffect
    处理屏幕特定的副作用——
    useEffect
    即使在屏幕被栈中其他屏幕覆盖时仍会运行。
  • 直接修改导航状态(v7开发模式下会被捕获,生产环境中会静默损坏状态)。
  • 自定义主题中缺少
    fonts
    属性——v7要求必须设置,否则会崩溃。
陷阱与边缘情况:
  • useFocusEffect
    回调必须包裹在
    useCallback
    中——否则效果会在每次渲染时触发,而非仅在焦点变化时触发
  • usePreventRemove
    仅在导航状态移除(返回、弹出、重置)时触发——屏幕仅失去焦点(推入、切换标签)时不会触发
  • 预加载的屏幕在实际导航到之前,无法分发导航操作或调用
    navigation.setOptions()
  • 屏幕
    options
    可以是对象或接收
    { route, navigation }
    的函数——当选项依赖路由参数时使用函数形式
  • headerSearchBarOptions
    要求ScrollView/FlatList设置
    contentInsetAdjustmentBehavior="automatic"
    以保证布局正确
  • v7中
    headerBackButtonDisplayMode
    替代了
    headerBackTitleVisible
    ——取值为"default"、"generic"或"minimal"
  • v7中移除了标签/抽屉的
    unmountOnBlur
    ——改用
    popToTopOnBlur: true
    useIsFocused
    模式
  • 开发模式下导航状态被冻结——如果之前直接修改状态,v7开发构建中会出现运行时错误
  • Android需要在
    MainActivity
    中设置
    RNScreensFragmentFactory
    ——否则Activity重启时会丢失View状态
  • Link
    组件从基于路径改为基于屏幕:使用
    <Link screen="Profile" params={{ userId }}>
    而非
    <Link to="/profile/123">
</red_flags>

<critical_reminders>

CRITICAL REMINDERS

重要提醒

All code must follow project conventions in CLAUDE.md
(You MUST declare a global
ReactNavigation.RootParamList
interface so
useNavigation
is type-safe without manual annotation)
(You MUST use
createNativeStackNavigator
for production apps -- the JS stack (
@react-navigation/stack
) is significantly slower and only needed for highly custom transitions)
(You MUST use
popTo()
to navigate back to a previous screen in the stack --
navigate()
in v7 no longer pops back to existing screens)
(You MUST wrap
useFocusEffect
callbacks in
useCallback
-- without it, the effect runs on every render, not just focus changes)
(You MUST NOT use
navigation.navigate('NestedScreen')
to reach screens in child navigators -- v7 removed implicit nested navigation; use explicit parent targeting)
Failure to follow these rules will cause untyped navigation, performance issues, broken back navigation, and runtime errors.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定
(你必须声明全局
ReactNavigation.RootParamList
接口,这样
useNavigation
无需手动注解就具备类型安全性)
(生产应用必须使用
createNativeStackNavigator
——JS栈(
@react-navigation/stack
)速度明显更慢,仅在需要高度自定义转场时使用)
(返回栈中之前的屏幕必须使用
popTo()
——v7中
navigate()
不再返回已有屏幕)
useFocusEffect
回调必须包裹在
useCallback
中——否则效果会在每次渲染时运行,而非仅在焦点变化时运行)
(禁止使用
navigation.navigate('NestedScreen')
访问子导航器中的屏幕——v7移除了隐式子导航;需显式指定父级目标)
不遵循这些规则会导致无类型导航、性能问题、返回导航失效和运行时错误。
</critical_reminders>