Expo 路由分屏视图

一个 Expo Router 子模块,提供原生分屏布局。

iOS
Bundled version:
~55.0.5

重要 SplitView 是一个 alpha 版 API,仅在 iOS 且 Expo SDK 55 及更高版本可用。该 API 可能会发生重大更改,尚未准备好用于生产环境。

expo-router/unstable-split-view 是 expo-router 的一个子模块,导出用于使用 平台原生系统拆分视图 构建拆分视图布局的组件。

有关本地和 Web 应用的基于文件的路由库的更多信息,请参阅 Expo Router 参考资料。

平台支持

🌐 Platform support

分屏视图仅适用于 iOS。在其他平台上,SplitView 组件会自动回退为标准的 Slot 导航器,从而确保你的应用在所有平台上都能正常工作,无需条件代码。

🌐 Split View is only available on iOS. On other platforms, the SplitView component automatically falls back to rendering as a standard Slot navigator, ensuring your app works across all platforms without conditional code.

iPhone 支持

🌐 iPhone support

在 iPhone 上,SplitView 会自动将所有列折叠为一个视图。一次只能看到一列。

🌐 On iPhone, SplitView automatically collapses all columns into a single view. Only one column is visible at a time.

选择初始列

🌐 Choosing the initial column

使用 topColumnForCollapsing 属性来控制在拆分视图折叠时显示哪一列:

🌐 Use the topColumnForCollapsing prop to control which column is displayed when the split view is collapsed:

<SplitView topColumnForCollapsing="primary">{/* ... */}</SplitView>

可接受的值为 primary、supplementary 和 secondary。未设置时,系统将使用默认行为。

🌐 Accepted values are primary, supplementary, and secondary. When not set, the system uses its default behavior.

在列之间导航

🌐 Navigating between columns

信息 .show() 方法需要 react-native-screens 4.24.0 或更高版本。SDK 55 包含 ~4.23.0,因此你需要手动安装 react-native-screens@~4.24.0 才能使用此功能。

使用 ref 通过 show 方法以编程方式显示特定列:

🌐 Use a ref to programmatically show a specific column with the show method:

import { useRef } from 'react'; import { Pressable, Text } from 'react-native'; import { SplitView } from 'expo-router/unstable-split-view'; import type { SplitHostCommands } from 'react-native-screens/experimental'; export default function Layout() { const ref = useRef<SplitHostCommands>(null); return ( <SplitView ref={ref} topColumnForCollapsing="primary"> <SplitView.Column> <Pressable onPress={() => ref.current?.show('secondary')}> <Text>Show main content</Text> </Pressable> </SplitView.Column> </SplitView> ); }

已知的限制

🌐 Known limitations

不能嵌套

导航层级中只能有一个 SplitView。尝试嵌套拆分视图将会导致错误。

🌐 There can only be one SplitView in the navigation hierarchy. Attempting to nest split views will result in an error.

不能在其他导航器中使用

SplitView 不能在另一个导航器内部使用(Slot 除外)。它必须在根布局层级使用。

仅限特定儿童

SplitView 只接受 SplitView.Column 和 SplitView.Inspector 作为直接子元素。其他组件将被忽略并发出警告。

标题尚无法自定义

分屏视图列中的标题(导航栏)无法自定义。目前尚不支持自定义标题配置。

🌐 The header (navigation bar) within split view columns cannot be customized. Custom header configurations are not yet supported.

有限的 API

当前的 API 表面最小,可能无法涵盖所有使用场景。未来的版本将添加额外的属性和配置选项。

🌐 The current API surface is minimal and may not cover all use cases. Additional props and configuration options will be added in future releases.

返回 iPhone 上的前一列

要返回到前一列,请点击导航栏中的系统返回按钮。未来的版本将增加对返回导航的更精细的程序控制。

🌐 To go back to a previous column, tap the system back button in the navigation bar. A future release will add more granular programmatic control over back navigation.

信息 注意: 我们正在积极开发 SplitView 并寻求反馈。你可以在 Discord 上分享你的想法,或者在 GitHub 上 提交问题,也可以使用本页底部的 反馈 按钮。

安装

🌐 Installation

要在你的项目中使用 expo-router/unstable-split-view,你需要在项目中安装 expo-router。请按照 Expo Router 的安装指南中的说明操作:

🌐 To use expo-router/unstable-split-view in your project, you need to install expo-router in your project. Follow the instructions from Expo Router's installation guide:

安装 Expo 路由

了解如何在项目中安装 Expo Router。

使用 SplitView.Column

🌐 Using SplitView.Column

SplitView.Column 定义了拆分视图布局中的附加列。你可以在主内容区域之前添加最多两列。

两栏布局

🌐 Two-column layout

带有主要内容的简单侧边栏:

🌐 A simple sidebar with main content:

app/_layout.tsx
import { Link } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { Text, Pressable } from 'react-native'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { return ( <SplitView> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1 }}> <Link href="/inbox"> <Pressable style={{ padding: 16 }}> <Text>Inbox</Text> </Pressable> </Link> <Link href="/sent"> <Pressable style={{ padding: 16 }}> <Text>Sent</Text> </Pressable> </Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

三栏布局

🌐 Three-column layout

带有辅助栏和主要内容的侧边栏:

🌐 A sidebar with supporting column and main content:

app/_layout.tsx
import { Link, useGlobalSearchParams } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { const params = useGlobalSearchParams(); return ( <SplitView> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1, gap: 16, padding: 16, }}> <Link href="/?col1=1" style={{ fontWeight: params.col1 === '1' ? 'bold' : 'normal' }}> Option 1 </Link> <Link href="/?col1=2" style={{ fontWeight: params.col1 === '2' ? 'bold' : 'normal' }}> Option 2 </Link> <Link href="/?col1=3" style={{ fontWeight: params.col1 === '3' ? 'bold' : 'normal' }}> Option 3 </Link> </SafeAreaView> </SplitView.Column> <SplitView.Column> <SafeAreaView edges={{ left: true, top: true }} style={{ flex: 1, gap: 16, padding: 16, }}> <Link href={`/?col1=${params.col1}&col2=1`}>Sub-Option 1</Link> <Link href={`/?col1=${params.col1}&col2=2`}>Sub-Option 2</Link> <Link href={`/?col1=${params.col1}&col2=3`}>Sub-Option 3</Link> </SafeAreaView> </SplitView.Column> </SplitView> ); }

使用 SplitView.Inspector

🌐 Using SplitView.Inspector

SplitView.Inspector 添加了一个从尾部滑入的补充列,可用于显示附加细节或元数据:

<SplitView> <SplitView.Column>{/* Sidebar */}</SplitView.Column> <SplitView.Inspector> <View style={{ flex: 1, padding: 16 }}> <Text>Inspector Panel</Text> </View> </SplitView.Inspector> </SplitView>

完整示例

🌐 Complete example

这是一个类似密码管理器的应用,有三列:

🌐 Here's a password manager-style app with three columns:

app
 _layout.tsx
 index.tsx
 [type]
  [id].tsx
  index.tsx
app/_layout.tsx
import { Link, Color, useGlobalSearchParams } from 'expo-router'; import { SplitView } from 'expo-router/unstable-split-view'; import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import { SafeAreaView } from 'react-native-screens/experimental'; export default function Layout() { return ( <SplitView showInspector> <SplitView.Column> <PasscodeList /> </SplitView.Column> <SplitView.Column> <PasswordElementList /> </SplitView.Column> <SplitView.Inspector> <InspectorContent /> </SplitView.Inspector> </SplitView> ); } function PasscodeList() { return ( <SafeAreaView edges={{ top: true, left: true }} style={style.passcodeList}> <PasscodeCard title="所有" param="all" /> <PasscodeCard title="通行密钥" param="passkeys" /> <PasscodeCard title="代码" param="codes" /> <PasscodeCard title="安全" param="security" /> <PasscodeCard title="已删除" param="deleted" /> </SafeAreaView> ); } const passkeys = ['Github', 'Google', 'Facebook', 'Twitter', 'Apple', 'Microsoft', 'Amazon']; const security = ['Admin1234', 'Root']; const all = [...passkeys, ...security]; function PasswordElementList() { const params = useGlobalSearchParams(); const data = (() => { switch (params.type) { case 'all': case undefined: return all; case 'passkeys': return passkeys; case 'security': return security; default: return []; } })(); return ( <ScrollView contentInsetAdjustmentBehavior="automatic" style={{ backgroundColor: undefined }}> {data.map(item => ( <PasswordElement key={item} title={item} /> ))} </ScrollView> ); } function PasscodeCard({ param, title }: { param: string; title: string }) { const params = useGlobalSearchParams(); const isActive = params.type === param; return ( <Link href={`/${param}/`} disabled={isActive} style={[ style.passcodeCard, { backgroundColor: isActive ? Color.ios.systemBlue : Color.ios.systemGray6, }, ]} asChild> <Pressable> <Text style={{ color: isActive ? 'white' : 'black', fontSize: 16 }}>{title}</Text> </Pressable> </Link> ); } function PasswordElement({ title }: { title: string }) { const params = useGlobalSearchParams(); const isActive = params.id === title; return ( <Link href={`/${params.type}/${title}/`} asChild> <Pressable style={{ backgroundColor: isActive ? Color.ios.systemBlue : undefined, padding: 12, }}> <SafeAreaView edges={{ left: true }}> <Text style={{ color: isActive ? 'white' : 'black', fontSize: 16 }}>{title}</Text> </SafeAreaView> </Pressable> </Link> ); } function InspectorContent() { return ( <View style={style.inspectorContent}> <Text>Inspector</Text> </View> ); } const style = StyleSheet.create({ passcodeList: { flex: 1, flexWrap: 'wrap', gap: 8, flexDirection: 'row', padding: 8, }, passcodeCard: { width: '48%', padding: 12, borderRadius: 12, justifyContent: 'center', alignItems: 'center', height: 50, }, inspectorContent: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });
app/index.tsx
import { Redirect } from 'expo-router'; export default function Index() { return <Redirect href="/all/" />; }
app/[type]/index.tsx
import { Color } from 'expo-router'; import { Text, View } from 'react-native'; export default function Index() { return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text style={{ color: Color.ios.label, fontSize: 24, fontWeight: 'bold' }}> Nothing is selected </Text> </View> ); }
app/[type]/[id].tsx
import { useLocalSearchParams } from 'expo-router'; import { Text, View } from 'react-native'; export default function Id() { const { id } = useLocalSearchParams(); return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Text>ID: {id}</Text> </View> ); }

应用接口

🌐 API

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

Components

SplitView

iOS

Type: React.Element<SplitViewProps>

For full list of supported props, see SplitHostProps

SplitViewProps

children

iOS
Optional • Type: ReactNode

Inherited Props

  • Omit<SplitHostProps, 'children'>

SplitView.Column

iOS

Type: React.Element<SplitViewColumnProps>

SplitViewColumnProps

children

iOS
Optional • Type: ReactNode

SplitView.Inspector

iOS 26+

Type: React.Element<SplitViewColumnProps>