This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
自定义导航器
编辑页面
了解如何在 Expo Router 中构建自己的导航器,以及库作者如何将现有导航器与路由器集成。
Expo Router 内置了适用于最常见模式的导航器 — Stack、Tabs、Native tabs 和 Drawer。如果这些导航器都不符合你的需求,你可以构建自己的导航器并将其用作布局,同时继续使用基于文件的路由、深度链接和类型化路由,体验与内置导航器完全一致。
请选择与你的目标相匹配的入口点:
- 应用开发者:为单个应用构建导航器,请使用
createStandardRouterNavigator。 - 库作者:发布可供 Expo Router 和 React Navigation 共用的可复用导航器,请使用
integrateWithRouter。
提示
createStandardRouterNavigator和integrateWithRouter这两个稳定 API 从 SDK 58 起可用。在 SDK 56 和 SDK 57 中,请改用unstable_createStandardRouterNavigator和unstable_integrateWithRouter。
对于将路由渲染为 Web 模态覆盖层的栈导航器,请参阅构建自定义 Web 模态框。
在应用中创建导航器
使用 createStandardRouterNavigator 将内容组件转换为可作为布局渲染的导航器。它需要两个参数:
NavigatorContent:一个渲染导航器 UI 的组件。它会接收当前的导航state、每个屏幕的descriptors、用于导航的actions,以及用于发送事件的emitter。router:要使用的路由行为。从expo-router中导入StackRouter以实现类似栈的导航,或导入TabRouter以实现类似标签页的导航。
下面的示例构建了一个最小的标签页导航器:
返回的导航器有一个用于声明屏幕的 .Screen 子组件,因此你可以像其他布局一样在 _layout 文件中使用它:
NavigatorContent 接收什么
类型化事件
如果你的导航器会发出事件,请在 NavigatorContentProps 的第二个类型参数中声明它们。每个键都是一个事件名,其值描述了该事件的 data 以及它是否 canPreventDefault。随后 emitter.emit 就会基于该映射进行类型检查——未知事件名和不匹配的负载都会被拒绝:
type TabsContentProps = NavigatorContentProps< { title?: string }, { tabPress: { data: undefined; canPreventDefault: true } } >; function TabsContent({ emitter }: TabsContentProps) { emitter.emit({ type: 'tabPress', canPreventDefault: true }); // ... }
createStandardRouterNavigator 会从组件推断事件映射,因此你无需在调用处再次传入。对于不发出任何事件的导航器,请省略第二个类型参数。
选项
createStandardRouterNavigator 和 integrateWithRouter 都接受一个可选的 options 对象作为第三个参数。使用 createProps 派生标准 state 和 actions 之外的导航器专属属性:
export const Tabs = createStandardRouterNavigator(TabsContent, TabRouter, { createProps: ({ state, dispatch }) => ({ activeRouteKey: state.routes[state.index].key, preload: (name: string) => dispatch({ type: 'PRELOAD', payload: { name } }), }), });
在 NavigatorContentProps 的第四个类型参数中声明 createProps 返回的属性,以便 NavigatorContent 以类型化方式接收它们:
type TabsContentProps = NavigatorContentProps< { title?: string }, // 本示例中没有自定义事件。 Record<string, never>, // 本示例中没有自定义导航器属性。 object, // 由 `createProps` 注入的属性。 { activeRouteKey: string; preload: (name: string) => void } >; function TabsContent({ activeRouteKey, preload }: TabsContentProps) { // ... }
提示
createProps接收经过 Expo Router 处理的state和原始dispatch。这些属于内部实现,不同版本之间可能会有小幅变更,因此如果传递给NavigatorContent的state和actions足以满足需求,建议优先使用它们。如果标准state、actions或emitter中缺少你需要的内容,请在 GitHub 上提交 issue。
集成现有导航器(库作者)
标准导航器 API
上面展示的 NavigatorContent 组件是一个标准导航器。它实现了由 standard-navigation 包定义的最小化、与框架无关的契约。你的内容接收到的 state、descriptors、actions 和 emitter,与上面的应用内导航器所使用的同一 API 完全一致。唯一的区别在于是谁创建了这个导航器。
createStandardRouterNavigator 是一个快捷方式:它会为你调用 standard-navigation 中的 createStandardNavigator,并一步将结果集成到 Expo Router 中。作为库作者,请自行调用 createStandardNavigator 并保留对导航器的引用:
由于 TabsContent 和 navigator 只依赖标准契约,同样的代码可以运行在 Expo Router、React Navigation 或任何其他实现了该契约的宿主上。你只需编写一次导航器,然后为每个框架提供一个轻量的集成入口点。
与 Expo Router 集成
使用 integrateWithRouter 将导航器接入 Expo Router:
返回的组件与 createStandardRouterNavigator 返回的组件完全相同,包括 .Screen 子组件和相同的选项。
为内置导航器添加属性
提示 本节中的辅助函数从 SDK 58 起可用。
当你的库封装了 Expo Router 导航器时,请将其 createProps 辅助函数传递给 integrateWithRouter。该辅助函数会添加 Expo Router 所需的导航器专属行为。
例如,按以下方式集成 JavaScript 栈:
如需更底层的实现,请使用 expo-router 中的 createBaseStackProps 或 createBaseTabProps,并添加导航器所需的行为。
库入口点
保持导航器内容和标准导航器与框架无关,然后为每个框架暴露一个入口点,这样使用者就可以导入与其应用匹配的集成方式:
.srcTabsContent.tsx实现标准导航器 API 的导航器 UIindex.ts根入口 — 导出与框架无关的导航器react-navigation.tsReact Navigation 入口 — 集成同一个导航器expo-router.tsExpo Router 入口 — integrateWithRouter(navigator, ...)package.json将子路径导出映射到各框架入口在库的 package.json 中,将每个入口点映射为一个子路径导出,并指向你的构建输出:
然后,使用者就可以根据自己的框架导入相应的集成方式(例如,import { Tabs } from 'my-tabs/expo-router'),而你则只需在一个地方维护导航器逻辑。
了解如何将同一个标准导航器与 React Navigation 集成,并阅读定义 NavigatorContent 接收到的 state、descriptors、actions 和 emitter 的契约。
自定义路由器行为
提示
extendRouter和extendRouterActions从 SDK 58 起可用。
如果你只需要处理或拒绝导航操作,请使用 extendRouterActions。返回一个结果以处理该操作,返回 null 以拒绝该操作,或返回 undefined 以交由基础路由器处理。
如果你需要自定义其他路由器成员,例如 actionCreators、getStateForRouteFocus 或 normalizeState,请使用 extendRouter。你未返回的成员会继承自基础路由器。
下面的示例添加了一个 CLEAR 操作及其对应的操作创建器:
这两个辅助函数都会提供 baseRouter、options 和 nextKey。使用 baseRouter 委托现有行为,使用 options 读取传递给路由器工厂的值,并在向状态添加路由时使用 nextKey。