拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Expo Router 路由结构完全指南:从文件约定到 Stacks/Tabs 实战(基于 building-native-ui 技能文档)

Expo Router 路由结构完全指南从文件约定到 Stacks/Tabs 实战基于 building-native-ui 技能文档【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,400 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills导读本篇以 AASagentic-awesome-skills仓库中 Expo 官方building-native-ui技能的 route-structure.md 为核心骨架系统讲解 Expo Router 的路由文件约定、动态路由、分组路由、Stacks 与 Tabs 嵌套、Array Routes 多栈共享、布局文件与 404 处理等关键机制。读完本文你将掌握一套可直接落地的 Expo Router 工程目录组织规范并能结合本仓库内 SKILL.md、tabs.md、search.md 等配套参考搭建出目录清晰、URL 简洁、支持多 Tab 独立导航历史的原生应用。一、文件约定app目录的黄金法则Expo Router 以文件系统作为路由声明所有路由都必须放置在app目录下。以下约定是构建任何 Expo Router 应用的地基来自 route-structure.md路由归属路由文件一律放在app目录。动态路由使用[]表示动态段例如[id].tsx匹配任意单段路径。命名禁区路由文件绝不能命名为(foo).tsx——因为(foo)是分组语法应改用(foo)/index.tsx。URL 分组使用(group)形式的目录来简化对外公开的 URL 结构分组名不会出现在 URL 中。禁止混放组件绝不把组件、类型或工具函数放进app目录它们应放在components/、utils/等独立目录。这是本技能反复强调的 anti-pattern。纯路由目录app目录只应包含路由文件和_layout文件且每个文件必须默认导出一个组件。根路由兜底确保应用始终存在匹配/的路由否则应用会呈现空白页该路由可以位于某个分组内。Stack 必用布局定义导航栈时一律通过_layout.tsx文件完成。此外SKILL.md 的代码风格一节对文件组织提出了补充要求文件名一律使用 kebab-case例如comment-card.tsx重构导航时必须删除旧的路由文件避免残留路由干扰匹配文件名中禁止使用特殊字符在tsconfig.json中配置路径别名如/components/...重构时优先使用别名而非相对导入。二、动态路由方括号声明动态段动态路由用方括号声明可变路径段参数名与文件名一一对应app/ users/ [id].tsx # 匹配 /users/123、/users/abc [id]/ posts.tsx # 匹配 /users/123/posts[id].tsx可以匹配任意单段值如123或abc[id]/posts.tsx则是在动态段之下的子路由两者可以并存并各司其职。2.1 Catch-All 路由当一段路径需要匹配任意层级时使用[...slug]语法app/ docs/ [...slug].tsx # 匹配 /docs/a、/docs/a/b、/docs/a/b/cCatch-All 路由特别适合文档站、博客这类深度不确定的内容结构——无论/docs下嵌套多少级目录都能被同一份代码接管并渲染。三、Query 参数与 Pathname 读取3.1 useLocalSearchParams读取查询参数在页面组件内通过useLocalSearchParams读取 URL 查询参数与动态段参数import { useLocalSearchParams } from expo-router; function Page() { const { id } useLocalSearchParams{ id: string }(); }动态路由参数的命名与文件名严格对应[id].tsx→useLocalSearchParams{ id: string }()[slug].tsx→useLocalSearchParams{ slug: string }()这意味着动态段和?keyvalue查询串在 Expo Router 中会被统一合并进 LocalSearchParams读取方式完全一致。3.2 usePathname读取当前路径需要获取当前完整路径时使用usePathnameimport { usePathname } from expo-router; function Component() { const pathname usePathname(); // e.g. /users/123 }这在实现“当前页高亮”“面包屑导航”“埋点上报”等场景时非常有用。四、分组路由 (Group Routes)组织不改变 URL用圆括号声明分组目录分组名只用于组织代码不会出现在 URL 中app/ (auth)/ login.tsx # URL: /login register.tsx # URL: /register (main)/ index.tsx # URL: / settings.tsx # URL: /settings分组的三个典型用途组织相关路由把登录/注册归入(auth)把主界面归入(main)让目录结构直接反映业务域应用不同布局每个分组都可以有自己的_layout.tsx从而对整组路由施加不同的导航容器或配置保持 URL 简洁URL 中不会出现(auth)、(main)这类实现细节。五、Stacks 与 Tabs 的嵌套结构Tab 内的独立导航栈当应用使用 Tabs 时header 与页面标题必须设置在嵌套在每个 Tab 内部的 Stack 中。这样每个 Tab 都拥有独立的导航历史和各自的 header而根布局通常不显示 header。核心操作要点在 Tab 布局上把headerShown设为false使用(group)路由简化公开 URL结构调整时可能需要删除或重构已有的路由文件以适配新结构。标准结构示例app/ _layout.tsx — Tabs / (home)/ _layout.tsx — Stack / index.tsx — ScrollView / (settings)/ _layout.tsx — Stack / index.tsx — ScrollView / (home,settings)/ info.tsx — ScrollView / (shared across tabs)这里(home,settings)/info.tsx是跨 Tab 共享的页面——它同时隶属于 home 与 settings 两个分组因此两个 Tab 都能 push 到它。5.1 为什么 ScrollView 必须是第一个子组件route-structure.md 的示例中每个页面都是ScrollView /这与 SKILL.md 的行为规范一致路由属于 Stack 时其第一个子组件几乎总是带contentInsetAdjustmentBehaviorautomatic的ScrollView为响应式考虑根组件一律包在滚动视图中用contentInsetAdjustmentBehaviorautomatic取代SafeAreaView以获得更智能的安全区适配FlatList、SectionList同样应设置contentInsetAdjustmentBehaviorautomatic。这样从 tab 切换到页面内容时滚动与安全区行为都保持原生观感。六、Array Routes多栈共享屏幕的高级布局(index,settings)形式的 Array Route 用于创建多个 Stack特别适合需要跨栈共享屏幕的 Tab 应用app/ _layout.tsx — Tabs / (index,settings)/ _layout.tsx — Stack / index.tsx — ScrollView / settings.tsx — ScrollView /它需要一个带显式 anchor 路由的专用布局// app/(index,settings)/_layout.tsx import { useMemo } from react; import Stack from expo-router/stack; export const unstable_settings { index: { anchor: index }, settings: { anchor: settings }, }; export default function Layout({ segment }: { segment: string }) { const screen segment.match(/\((.*)\)/)?.[1]!; const options useMemo(() { switch (screen) { case index: return { headerRight: () / }; default: return {}; } }, [screen]); return ( Stack Stack.Screen name{screen} options{options} / /Stack ); }关键机制解读Layout接收的segmentprop 就是当前激活的分组名如(index)通过segment.match(/\((.*)\)/)?.[1]!提取出index或settings布局根据当前激活的屏幕动态渲染对应的Stack.Screen因此两个 Tab 共享同一个 Stack 容器可自由 push 公共详情页unstable_settings中为每个入口指定anchorv4 中取代了initialRouteName确保每个 Tab 都有明确的初始路由。6.1 完整的生产级 App 结构示例结合动态路由、Array Routes 与目录分离一份完整结构如下app/ _layout.tsx — NativeTabs / (index,search)/ _layout.tsx — Stack / index.tsx — Main list search.tsx — Search view i/[id].tsx — Detail page components/ theme.tsx list.tsx utils/ storage.ts use-search.ts注意components/与utils/与app/平级——这正是“绝不把组件与工具放进 app 目录”约定的落地形态。i/[id].tsx展示了 Array Route 内部同样可以使用动态段来承载详情页。七、布局文件Layout Files每个目录的导航容器每个目录都可以有一个_layout.tsx用于包裹该目录下的全部路由// app/_layout.tsx import { Stack } from expo-router/stack; export default function RootLayout() { return Stack /; }// app/(tabs)/_layout.tsx import { NativeTabs, Icon, Label } from expo-router/unstable-native-tabs; export default function TabLayout() { return ( NativeTabs NativeTabs.Trigger nameindex LabelHome/Label Icon sfhouse.fill / /NativeTabs.Trigger /NativeTabs ); }7.1 与 NativeTabs 的组合SDK 54/55第二个示例中的NativeTabs来自 tabs.md 推荐的expo-router/unstable-native-tabs。SDK 55 的推荐写法是组件化 APIimport { NativeTabs } from expo-router/unstable-native-tabs; export default function TabLayout() { return ( NativeTabs minimizeBehavioronScrollDown NativeTabs.Trigger nameindex NativeTabs.Trigger.Icon sfhouse.fill mdhome / NativeTabs.Trigger.LabelHome/NativeTabs.Trigger.Label NativeTabs.Trigger.Badge9/NativeTabs.Trigger.Badge /NativeTabs.Trigger NativeTabs.Trigger name(search) rolesearch NativeTabs.Trigger.LabelSearch/NativeTabs.Trigger.Label /NativeTabs.Trigger /NativeTabs ); }需要注意的规则来自 tabs.md每个 Tab 都必须有一个 Trigger且NativeTabs.Trigger的name必须与路由名完全一致包括圆括号例如NativeTabs.Trigger name(search)NativeTabs 不渲染 header必须在每个 Tab 内部嵌套 Stack 来提供导航 headerTabs 必须是静态的——运行时动态增删 Tab 会重挂载导航器并丢失状态搜索类 Tab 建议放在最后便于与搜索栏结合。7.2 与 JS Tabs 的差异速查JS TabsNative TabsTabs.ScreenNativeTabs.Triggeroptions{{ title }}NativeTabs.Trigger.Labeloptions{{ tabBarIcon }}NativeTabs.Trigger.IcontabBarBadgeoptionNativeTabs.Trigger.BadgeProps 驱动 API组件化 API内置 header需嵌套Stack提供 header八、路由设置Route Settingsanchor 与 unstable_settings通过导出unstable_settings来配置路由行为export const unstable_settings { anchor: index, };重要变更在 Expo Router v4 中initialRouteName被重命名为anchor。在 Array Routes 中每个入口都可通过unstable_settings独立指定自己的 anchor 路由见第六节的index: { anchor: index }这是多栈共享布局能够正确初始化的关键。九、404 处理not-found.tsx创建not-found.tsx文件处理所有未匹配的路由// app/not-found.tsx import { Link } from expo-router; import { View, Text } from react-native; export default function NotFound() { return ( View TextPage not found/Text Link href/Go home/Link /View ); }该文件是 Expo Router 约定俗成的“兜底路由”——任何没有对应文件的 URL 都会落到这里配合第一节的“根路由兜底”约定应用永远不会出现空白页或死链。十、实战要点汇总把路由结构落到真实页面10.1 页面标题放在 Stack 而非页面文本SKILL.md 明确要求一律使用导航栈标题Stack title而不是页面内的自定义文本元素。在 Stack 布局中通过Stack.Screen的 options 设置Stack.Screen options{{ title: Home }} /10.2 在 Stack 布局中统一配置 header结合 Array Route 与useLocalSearchParams的实际需求一个带搜索与透明 header 的共享 Stack 布局通常写成// app/(index,search)/_layout.tsx import { Stack } from expo-router/stack; import { colors } from /theme/colors; export default function Layout({ segment }) { const screen segment.match(/\((.*)\)/)?.[1]!; const titles: Recordstring, string { index: Items, search: Search }; return ( Stack screenOptions{{ headerTransparent: true, headerShadowVisible: false, headerLargeTitle: true, headerTitleStyle: { color: colors.label }, headerBackButtonDisplayMode: minimal, }} Stack.Screen name{screen} options{{ title: titles[screen] }} / Stack.Screen namei/[id] options{{ headerLargeTitle: false }} / /Stack ); }10.3 搜索页与查询参数的配合路由结构规划好后搜索栏可以直接挂在 Stack header 上详见 search.mdStack.Screen nameindex options{{ headerSearchBarOptions: { placeholder: Search, onChangeText: (event) console.log(event.nativeEvent.text), }, }} /若搜索 Tab 使用rolesearch的 NativeTabs搜索栏会与 Tab 栏无缝集成配合useLocalSearchParams即可实现“列表 → 详情”的完整检索闭环。10.4 自定义 header 工具栏iOSSDK 55对于需要 header 按钮的页面Stack.Toolbar提供原生 iOS 工具栏详见 toolbar-and-headers.md它作为 Stack 的兄弟节点书写在页面组件内 {/* ScrollView 必须是屏幕的第一个子组件 */} ScrollView style{{ flex: 1 }} contentInsetAdjustmentBehaviorautomatic {/* Screen content */} /ScrollView Stack.Screen.Title largeFolders/Stack.Screen.Title Stack.Toolbar placementright Stack.Toolbar.Button iconfolder.badge.plus onPress{() {}} / Stack.Toolbar.Button onPress{() {}}Edit/Stack.Toolbar.Button /Stack.Toolbar /placement支持leftheader 左侧、rightheader 右侧与bottom底部工具栏默认值注意placementbottom只能在屏幕组件内使用不能写在布局文件中。十一、常见误区与规避建议结合 route-structure.md 与 SKILL.md以下是实践中最容易踩的坑在app目录混放组件/工具这是最严重的 anti-pattern会导致路由扫描到非页面文件而报错或行为异常组件进components/工具进utils/。(foo).tsx文件命名圆括号是分组保留语法单个文件不能直接叫(foo).tsx请用(foo)/index.tsx。忘记根路由app下没有匹配/的路由时应用为空白页可将 index 放入某个分组内解决。Trigger name 不匹配NativeTabs 中name必须与路由名完全一致含圆括号如(search)。重构不删旧路由移动或重构导航时务必同步删除旧路由文件否则会被意外匹配。JS Tab 与 Native Tab 混用NativeTabs 不渲染 header需要 header 就在每个 Tab 内嵌套 Stack并在 Tab 布局关闭headerShown。结语Expo Router 的app目录即路由掌握文件约定、动态段、分组与 Array Routes就等于掌握了整个导航体系的设计语言。本文内容以 route-structure.md 为骨架并补充了 SKILL.md、tabs.md、search.md、toolbar-and-headers.md 中的配套约定。实际编码时请以官方文档为准核对版本差异尤其是 v4 中initialRouteName→anchor的更名以及 SDK 54/55 之间 NativeTabs API 的差异并始终遵循“ScrollView 作为首个子组件、Stack title 承载标题、组件与工具隔离在 app 之外”这三条核心纪律即可构建出结构清晰、体验原生的 Expo 应用。【免费下载链接】agentic-awesome-skillsAAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,400 agentic skills. Includes CLI, local MCP, catalog, plugins, and Workbench.项目地址: https://gitcode.com/gh_mirrors/an/agentic-awesome-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门