> For the complete documentation index, see [llms.txt](https://developers-apps-in-toss.toss.im/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/react-native/screen-navigation/navigation.md).

# 页面跳转

在 Granite 应用中，可以轻松处理屏幕切换、历史记录管理、参数传递等路由功能。其内部是 [React Navigation](https://reactnavigation.org/)为基础运行，因此可以直接使用熟悉的 API。

{% hint style="info" %}
**WebView 路由**

在 WebView 环境中，会完全遵循项目中设置的网页路由规则（例如：React Router）。
{% endhint %}

### 路由示例结构

路由示例共由 3 个页面（`page-a`, `page-b`, `page-c`）组成。

```
root
├─── pages
│    ├─── page-a.tsx
│    ├─── page-b.tsx
│    └─── page-c.tsx
└─── src
     └─── ...
```

<details>

<summary>`page-a.tsx` 源代码</summary>

```tsx
// page-a.tsx
import { StyleSheet, View, Text, Pressable } from 'react-native';
import { createRoute, useNavigation } from '@granite-js/react-native';

export const Route = createRoute('/page-a', {
  validateParams: (params) => params,
  component: PageA,
});

function PageA() {
  const navigation = useNavigation();

  const handlePress = () => {
    navigation.navigate('/page-b');
  };

  return (
    <View style={[styles.container, { backgroundColor: '#3182f6' }]}>
      <Text style={styles.text}>页面 A</Text>
      <Pressable onPress={handlePress}>
        <Text style={styles.buttonLabel}>前往 B 页面</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

</details>

<details>

<summary>`page-b.tsx` 源代码</summary>

```tsx
// page-b.tsx
import { createRoute, useNavigation } from '@granite-js/react-native';
import { StyleSheet, View, Text, Pressable } from 'react-native';

export const Route = createRoute('/page-b', {
  validateParams: (params) => params,
  component: PageB,
});

function PageB() {
  const navigation = useNavigation();

  // 用于返回上一屏幕的函数。
  const handlePressBackButton = () => {
    if (navigation.canGoBack()) {
      navigation.goBack();
    } else {
      console.warn('无法移动到上一屏幕。');
    }
  };

  const handlePressNextButton = () => {
    navigation.navigate('/page-c', {
      message: '你好!',
      date: new Date().getTime(),
    });
  };

  return (
    <View style={[styles.container, { backgroundColor: '#fe9800' }]}>
      <Text style={styles.text}>页面 B</Text>
      <Pressable onPress={handlePressBackButton}>
        <Text style={styles.buttonLabel}>返回上一页</Text>
      </Pressable>
      <Pressable onPress={handlePressNextButton}>
        <Text style={styles.buttonLabel}>前往 C 页面</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

</details>

<details>

<summary>`page-c.tsx` 源代码</summary>

```tsx
// page-c.tsx
import { useNavigation, createRoute } from '@granite-js/react-native';
import { CommonActions } from '@granite-js/native/@react-navigation/native';
import { StyleSheet, View, Text, Pressable } from 'react-native';

export const Route = createRoute('/page-c', {
  validateParams: (params) => params as { message: string; date: number },
  component: PageC,
});

function PageC() {
  const navigation = useNavigation();
  const params = Route.useParams();

  const handlePressHomeButton = () => {
    navigation.dispatch((state) => {
      return CommonActions.reset({
        ...state,
        index: 0,
        routes: state.routes.filter((route) => route.name === '/page-a'),
      });
    });
  };

  return (
    <View style={[styles.container, { backgroundColor: '#f04452' }]}>
      <Text style={styles.text}>{params.message}</Text>
      <Text style={styles.text}>{params.date}</Text>
      <View style={styles.line} />
      <Text style={styles.text}>页面 C</Text>
      <Pressable onPress={handlePressHomeButton}>
        <Text style={styles.buttonLabel}>前往起始页</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

</details>

### 页面 A：页面跳转

[`useNavigation`](https://reactnavigation.org/docs/use-navigation)用于处理屏幕之间的跳转。 [`navigate`](https://reactnavigation.org/docs/navigation-actions/#navigate) 可以将要跳转的屏幕路径和所需数据一起传递给该方法。

```tsx
// page-a.tsx
import { createRoute, useNavigation } from '@granite-js/react-native'; // [!code highlight]
import { StyleSheet, View, Text, Pressable } from 'react-native';

export const Route = createRoute('/page-a', {
  validateParams: (params) => params,
  component: PageA,
});

function PageA() {
  const navigation = useNavigation(); // [!code highlight]
  // [!code highlight:4]
  const handlePress = () => {
    navigation.navigate('/page-b');
  };

  return (
    <View style={[styles.container, { backgroundColor: '#3182f6' }]}>
      <Text style={styles.text}>页面 A</Text>
      <Pressable onPress={handlePress}>
        <Text style={styles.buttonLabel}>前往 B 页面</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

#### 要点

* `useNavigation` 使用 Hook 获取 `navigation` 对象。
* `navigation.navigate('/page-b')`会跳转到 'B' 页面。

### 页面 B：返回上一屏幕

[`goBack`](https://reactnavigation.org/docs/navigation-actions/#goback) 方法可以返回上一屏幕。但如果没有上一屏幕记录，可能会发生错误，因此， [`canGoBack`](https://reactnavigation.org/docs/navigation-prop/#cangoback)要先进行确认。

```tsx
// page-b.tsx
import { createRoute, useNavigation } from '@granite-js/react-native'; // [!code highlight]
import { StyleSheet, View, Text, Pressable } from 'react-native';

export const Route = createRoute('/page-b', {
  validateParams: (params) => params,
  component: PageB,
});

function PageB() {
  const navigation = useNavigation(); // [!code highlight]

  // 用于返回上一屏幕的函数。 // [!code highlight:8]
  const handlePressBackButton = () => {
    if (navigation.canGoBack()) {
      navigation.goBack();
    } else {
      console.warn('无法移动到上一屏幕。');
    }
  };

  const handlePressNextButton = () => {
    navigation.navigate('/page-c', {
      message: '你好!',
      date: new Date().getTime(),
    });
  };

  return (
    <View style={[styles.container, { backgroundColor: '#fe9800' }]}>
      <Text style={styles.text}>页面 B</Text>
      <Pressable onPress={handlePressBackButton}>
        <Text style={styles.buttonLabel}>返回上一页</Text>
      </Pressable>
      <Pressable onPress={handlePressNextButton}>
        <Text style={styles.buttonLabel}>前往 C 页面</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

#### 要点

* `canGoBack()`确认是否存在上一屏幕，如果有则 `goBack()`。
* `navigate('/page-c', { message: '你好!', date: new Date().getTime() })`来传递数据并跳转到 'C' 页面。

### 页面 C：使用传递的数据

`Route.useParams` Hook 用于获取从其他屏幕传递过来的数据。

这时， `createRoute.validateParams` 如果设置该选项，就可以在进行类型验证（Type-Safe）的同时访问传递过来的数据。这样可以防止因数据格式错误导致的报错。

```tsx
// page-c.tsx
import { createRoute, useNavigation } from '@granite-js/react-native'; // [!code highlight]
import { CommonActions } from '@granite-js/native/@react-navigation/native';
import { StyleSheet, View, Text, Pressable } from 'react-native';

// [!code highlight:5]
export const Route = createRoute('/page-c', {
  validateParams: (params) => params as { message: string; date: number },
  component: PageC,
});

function PageC() {
  const navigation = useNavigation();
  const params = Route.useParams(); // [!code highlight:7]
  // 或者也可以像下面这样使用。
  // import { useParams } from '@granite-js/react-native';
  //
  // const params = useParams({
  //   from: '/page-b',
  // });

  const handlePressHomeButton = () => {
    navigation.dispatch((state) => {
      return CommonActions.reset({
        ...state,
        index: 0,
        routes: state.routes.filter((route) => route.name === '/page-a'),
      });
    });
  };

  return (
    <View style={[styles.container, { backgroundColor: '#f04452' }]}>
      <Text style={styles.text}>{params.message}</Text> // [!code highlight]
      <Text style={styles.text}>{params.date}</Text> // [!code highlight]
      <View style={styles.line} />
      <Text style={styles.text}>页面 C</Text>
      <Pressable onPress={handlePressHomeButton}>
        <Text style={styles.buttonLabel}>前往起始页</Text>
      </Pressable>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 16,
    padding: 16,
  },
  text: {
    color: 'white',
    fontSize: 24,
  },
  buttonLabel: {
    color: 'white',
  },
});
```

#### 要点

* `Route.useParams` 使用 Hook 就可以访问从 URL 传递过来的数据（参数）。
* `createRoute.validateParams` 设置选项后，可以在进行数据类型验证（Type-Safe）的同时安全使用。

### 定义页面参数类型

为每个页面定义如下 Route 组件。在这里 `validateParams` 选项定义该页面可接收的参数类型。

```tsx
export const Route = createRoute('/page-c', {
  validateParams: (params) => params as { message: string; date: number }, // [!code highlight]
  component: PageC,
});
```

在上面的代码中， `validateParams`是 `消息`和 `date`这两个字段的参数定义为类型。

通过这样，在其他代码中 `useNavigate`或 `useParams`时，通过类型检查可以明确知道需要的路径和要传递的数据。这样可以提高代码的安全性和可读性。

***

#### 自动生成类型定义

在开发模式下， `pages/` 当向目录中添加文件时，会自动生成类型定义，因此无需执行单独的命令。

#### 生成文件示例

自动生成的文件如下。由于该文件是自动生成的，因此不需要手动修改。

```tsx
// src/router.gen.ts

/* eslint-disable */
// 此文件由 @granite-js/react-native 自动生成。请勿编辑。
import { Route as _AboutRoute } from '../pages/about';
import { Route as _IndexRoute } from '../pages/';

declare module '@granite-js/react-native' {
  interface RegisterScreen {
    '/about': ReturnType<typeof _AboutRoute.useParams>;
    '/': ReturnType<typeof _IndexRoute.useParams>;
  }
}
```

#### 要点

* 如果将各页面接收的参数类型 `createRoute.validateParams` 通过选项预先定义好， `navigate`和 `params` 使用时就可以进行类型检查，因此可以更安全地编写代码。
* 在开发模式下， `pages/` 当向目录中添加文件时，会自动生成类型定义，因此无需另外执行命令。

这样使用 React Navigation，就能轻松处理屏幕间跳转，并通过传递数据或操作历史记录实现多种 UX。另外，与 TypeScript 一起使用时，还能编写安全且稳健的代码。

### 初始化路由状态

页面 A → 页面 B → 页面 C 的顺序跳转后，当前状态可以如下图所示。

页面 A、页面 B、页面 C 按顺序 `routes` 保留在记录中， `index` 值指向最后跳转到的页面 C 的位置 2。

`reset`可以用于初始化页面跳转记录。例如，在按“页面 A → B → C”顺序跳转后，如果想回到“页面 A”并删除 B 和 C 的记录， [`CommonActions.reset`](https://reactnavigation.org/docs/navigation-actions/#reset)使用。

```tsx
navigation.dispatch(
  CommonActions.reset({
    index: 0,
    routes: [{ name: '/page-a' }],
  }),
);
```

#### 要点

* `CommonActions.reset`就可以只保留特定页面在记录中，并删除其余页面的记录。

### 参考

* [React Navigation 官方文档](https://reactnavigation.org/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers-apps-in-toss.toss.im/documentation/api-and-sdk-zh/react-native/screen-navigation/navigation.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
