Expo Router v55:原生导航更强,Web 能力也更进一步

Expo Router v55 升级了导航功能,带来新的 Stack API、原生标签页、动态颜色和工具栏,以及实验性的 SSR 和数据加载器。

中文
复制
Expo Router v55: more native navigation, more powerful web

Expo Router v55 来了(没错,我们一口气跨了 49 个版本)——这是朝着 Expo Router 发布以来一直追求的目标迈出的重要一步:让应用更贴近原生体验,同时支持更复杂的 Web 场景,让你真正做到只写一次代码。

这个版本同时做了两件大事:

让原生 UI 成为路由的一等公民。 全新的 Stack API、工具栏、原生标签页、动态平台配色、缩放转场和分屏支持,让你用构建应用其余部分的方式来构建导航 UI:用 React 组件,直接组合在屏幕所在的位置。

拓展了「只写一次」在 Web 上的含义。 Expo Router 现在有了更清晰的运行时方案——expo-server、可配置的服务器响应头,以及实验性的服务端渲染和数据加载器,让你无需另建一套 Web 技术栈,就能交付更快的页面、更好的 SEO 和更动态的内容。

X 上的帖子

注意,其中部分 API 仍处于实验阶段,可能会为了最佳体验而调整。截至 2026/02/xx,本文展示的 API 均为最新,但更新的示例请始终以 Expo Router 文档为准。

下面带你过一遍新内容、它们为什么重要,以及今天可以从哪里开始上手。

新的 Stack API

新的 Stack API 让你以声明式、组件级的方式控制原生头部。我们用可组合的 React 组件取代了 options API,你可以直接在屏幕里使用它们。设置头部样式、设置标题、添加搜索栏——全都在你的组件树里完成。

import { Stack } from "expo-router"

export default function IndexPage() {
	...
	return (
		<>
			{/* Screen content */}
			<Stack.Header style={{ backgroundColor: 'transparent' }} />
			<Stack.Screen.Title>Home</Stack.Screen.Title>
			<Stack.SearchBar onChangeText={handleSearchChange} />
		</>
	);
}

原生标签页

我们加入了对 Native Tabs 组件的支持。我们正在积极推进全平台完整支持,目标是在下个版本将该 API 标记为稳定。

原生标签页

在 Android 上,Native Tabs 默认使用 Material Design 3 动态配色,自动适应用户偏好的颜色。你可以通过在 NativeTabs.Trigger 上指定 md 属性来使用 Material Symbols——类似 iOS 上的 SF Symbols。SafeAreaView 现在已默认集成,内容不会再被标签栏遮挡。

<NativeTabs>
  <NativeTabs.Trigger name="(library)">
    <NativeTabs.Trigger.Icon
      sf="photo.fill.on.rectangle.fill"
      md="photo_library"
    />
    <NativeTabs.Trigger.Label>Library</NativeTabs.Trigger.Label>
  </NativeTabs.Trigger>
  ...
</NativeTabs>

在 iOS 上,我们新增了对底部附属视图的支持——一个悬浮在标签栏上方的浮动视图,从 iOS 26 开始可用。在 Expo Router 中,只需在 NativeTabs 布局里加入 NativeTabs.BottomAccessory 组件即可使用。

import { NativeTabs } from 'expo-router/unstable-native-tabs';
...
export default function Layout() {
  ...
  return (
	  <NativeTabs>
      ...
			<NativeTabs.BottomAccessory>
			  <MiniPlayer isPlaying={isPlaying} setIsPlaying={setIsPlaying} />
			</NativeTabs.BottomAccessory>
		</NativeTabs>
	);
}

在开发这个版本的过程中,我们引入了一些破坏性 API 变更。简短的迁移指南见这里

原生标签页的全部功能请查阅文档

动态颜色

动态颜色

新的 Color API 让你可以直接访问平台颜色:Color.ios.* 对应 iOS 系统颜色,Color.android.* 对应 Android 系统颜色,Color.android.dynamic.* 对应会随用户壁纸变化的 Material Dynamic 颜色。

import { Color } from "expo-router"

export default function Page() {
	// This needs to be declared within React Component
	const backgroundColor = Platform.select({
		android: Color.android.dynamic.surface,
		ios: Color.ios.systemBackground,
		default: '#fff'
	});
	...
	return (
		<ScrollView style={{ backgroundColor }}>
			...
		</ScrollView>
	);
}

关于 Color API 的更多内容见文档

工具栏

新的 Toolbar API 把原生 iOS 工具栏带到了 Expo Router,内置动画,并完整支持 liquid glass 效果。位置有三种可选:标题栏左侧、标题栏右侧,或固定在屏幕底部。

import { Stack } from "expo-router";

export default function FoldersScreen() {
	...
	return (
		<>
			{/* Screen content */}
			<Stack.Header style={{ backgroundColor: 'transparent' }} />
			
			<Stack.Screen.Title large>Folders</Stack.Screen.Title>
			
			<Stack.SearchBar onChangeText={handleSearchChange} />
			
			<Stack.Toolbar placement="right">
				<Stack.Toolbar.Button icon="folder.badge.plus" onPress={addFolder} />
        <Stack.Toolbar.Button onPress={activateEditMode}>Edit</Stack.Toolbar.Button>
			</Stack.Toolbar>
			
			<Stack.Toolbar placement="bottom">
				<Stack.Toolbar.SearchBarSlot />
				<Stack.Toolbar.Spacer />
				<Stack.Toolbar.Button icon="square.and.pencil" onPress={addNew} />
			</Stack.Toolbar>
		</>
	);
}

关于工具栏的更多内容见文档

缩放转场

新的 Apple zoom API 为 Apple 平台上的任意 <Link /> 带来手势驱动、可中断的原生共享元素转场,其他平台则使用默认行为。我们拆解了底层的 UIKit API,并以 React 优先的方式将其重新设计为分层效果。

从 Link 到路由的完整缩放。

将 Link 内的单个视图缩放到目标路由。

将单个视图缩放到下一个路由中的共享视图。

这让你拥有完全的控制权,同时还有开箱即用的优雅降级。没有新依赖,没有新导入,仍然是你已经在用的那个强大的 Link。

要开始使用,在 Link.Trigger 上设置 withAppleZoom

<Link href="..." asChild>
	<Link.Trigger withAppleZoom>
		<Pressable style={{ width: '50%', aspectRatio: 1 }}>
			<Image source={...} />
		</Pressable>
	</Link.Trigger>
</Link>

关于缩放转场的更多内容见文档

expo-server

@expo/server 包已更名为 expo-server,现在作为 Expo SDK 的一部分发布。它为 Expo Router 项目提供运行时服务端 API,并内置了面向 EAS Hosting 以及其他平台的部署适配器。

// app/api/user+api.ts
import { deferTask, setResponseHeaders } from 'expo-server';
import telemetry from '@/telemetry';

export function GET() {
  deferTask(async () => {
    telemetry.track();
  });

  setResponseHeaders({ 'Cache-Control': 'max-age=60' });

  return Response.json({ user: 'anonymous' });
}

只要是在 Expo Router 中编写服务端代码的地方,运行时 API 都受支持,包括 API 路由(如上所示)、中间件、数据加载器,以及 React Server Components 或函数。在生产环境中,expo-server 不仅能在 EAS Hosting 上运行,也支持 Express、Vercel、Netlify、Cloudflare Workers 和 Bun。

文档中进一步了解 expo-server

服务器 headers

现在,你可以直接在应用配置中为路由定义自定义 HTTP headers,适用于安全 headers、缓存策略、cookies 等场景。

{
  "expo": {
    "plugins": [
      ["expo-router", {
        "headers": {
          "X-Frame-Options": "DENY",
          "Cache-Control": "public, max-age=3600"
        }
      }]
    ]
  }
}

Headers 会作用于 HTML 和 API 路由的响应(但不包括静态资源!)。当你需要为 expo-sqlite on web 这类功能实现跨源隔离时,这一点尤其有用。

文档中进一步了解服务器 headers。

实验性服务器渲染

我们为 Expo 应用新增了服务器端渲染的实验性支持。此前,Expo Router 仅支持静态站点生成(SSG),即在构建时预生成 HTML。有了服务器端渲染(SSR),HTML 会在每次请求时动态生成,非常适合个性化内容、实时数据和更好的 SEO。

{
  "expo": {
    "web": {
      "output": "server"
    },
    "plugins": [
      ["expo-router", {
        "unstable_useServerRendering": true
      }]
    ]
  }
}

文档中进一步了解实验性服务器渲染。

实验性 data loaders

我们还为 data loaders 新增了实验性支持。Data loaders 让你能在服务器上获取数据,并通过一个新的 useLoaderData hook 在组件中访问,静态渲染和服务器渲染均适用。

这样,你就可以把数据获取和路由组件放在一起,同时又能安全地把敏感数据留在服务器端(比如环境变量),也不必再单独管理 API 路由。客户端代码只会看到 loader 函数的返回结果:

import { useLoaderData } from 'expo-router';
import { setResponseHeaders } from 'expo-server';

// Runs on the server, and has access to `process.env`
export async function loader() {
  // You can also use the runtime API from `expo-server`
  setResponseHeaders({ 'Cache-Control': 'max-age=60' });
  return fetch('https://api.example.com/data', {
    headers: { Authorization: `Bearer ${process.env.API_KEY}` }
  }).then(r => r.json());
}

export default function Home() {
  const data = useLoaderData<typeof loader>();
  return <Text>{data.title}</Text>;
}

文档中进一步了解。

静态渲染

在静态渲染模式下(应用配置中 web.output 设为 static 时),loaders 在构建时执行,你可以在部署前一次性预取所需数据。

服务器渲染

在服务器渲染下(应用配置中 web.output 设为 server,并启用实验性服务器渲染时),loaders 在请求时执行。如果你只是需要获取并展示数据,就不必再单独写 API 路由:

实验性分屏视图

我们与 React Native Screens 团队合作,为原生 Apple UISplitViewController 组件加入了实验性支持。在 iPad 上运行你的应用即可试用。

import { SplitView } from 'expo-router/unstable-split-view';

export default function RootLayout()
	return (
		<SplitView>
			<SplitView.Column>
				{/* Column content */}
			</SplitView.Column>
			<SplitView.Column>
				{/* Column content */}
			</SplitView.Column>
		</SplitView>
	);
}

文档中进一步了解分屏视图。

从 Expo Router v55 开始

Expo Router v55 归根结底只关乎一个承诺:即便代码库是共享的,你的应用默认也应该有原生质感。 到了 SDK 55,这个承诺具体了许多。

  • 用写 React 的方式构建原生导航 UI。 声明式 header、搜索、工具栏、标签页、平台配色,以及手势驱动的转场,让你不必再外挂自定义原生代码,就能做出精致的导航。

  • 更扎实的 Web 基础。 expo-server、服务端 header,以及实验性的 SSR 和数据加载器,带来更好的性能、更好的 SEO 和更动态的体验,同时保持应用架构一致。

  • 在更多形态的设备上提供更好的体验。 分屏视图这类特性让 Router 在 iPad 和更大屏幕上进一步迈入“真正的应用”的范畴。

如果你要升级,先看原生标签页的迁移说明,再挑一个新 API 用起来(Stack Header 或 Native Tabs 都是很好的第一步)。如果你要试实验性的 Web 特性,就像对待任何新平台能力一样:先上手折腾,必要时放在 flag 后面发布,然后把出问题的地方告诉我们,好让我们把它们打磨得平淡无奇、稳定可靠。

也给我们看看你在做什么!在社交平台上 @ 我们,发私信,或者到 Discord 里分享。我们很想看看你做出了什么!

来源: Expo Blog← 返回首页