React Native Tab View
React Native Tab View 是一个跨平台的 Tab View 组件,用于 React Native,它在 Android 和 iOS 上使用 react-native-pager-view
实现,在 Web、macOS 和 Windows 上使用 PanResponder 实现。
默认情况下,它遵循 Material Design 指南,但您也可以使用自己的自定义标签栏或将标签栏放置在底部。
此包不与 React Navigation 集成。如果您想将标签视图与 React Navigation 的导航系统集成,例如,希望在标签栏中显示屏幕,并能够使用 navigation.navigate
等在它们之间导航,请使用 Material Top Tab Navigator 代替。
安装
要使用此包,请在项目根目录中打开终端并运行
- npm
- Yarn
- pnpm
npm install react-native-tab-view
yarn add react-native-tab-view
pnpm add react-native-tab-view
接下来,如果您计划支持 iOS 和 Android,请安装 react-native-pager-view
。
如果您正在使用 Expo,为了确保您获得兼容版本的库,请运行
expo install react-native-pager-view
如果您没有使用 Expo,请运行以下命令
- npm
- Yarn
- pnpm
npm install react-native-pager-view
yarn add react-native-pager-view
pnpm add react-native-pager-view
完成了!现在您可以在您的设备/模拟器上构建并运行该应用了。
快速开始
import * as React from 'react';
import { View, useWindowDimensions } from 'react-native';
import { TabView, SceneMap } from 'react-native-tab-view';
const renderScene = SceneMap({
first: FirstRoute,
second: SecondRoute,
});
const routes = [
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
];
export default function TabViewExample() {
const layout = useWindowDimensions();
const [index, setIndex] = React.useState(0);
return (
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
/>
);
}
Snack 上的更多示例
API 参考
该包导出一个 TabView
组件,您可以使用它来渲染标签视图,以及一个 TabBar
组件,它是默认的标签栏实现。
TabView
负责渲染和管理标签的容器组件。默认情况下遵循 Material Design 样式。
基本用法如下所示
<TabView
navigationState={{ index, routes }}
onIndexChange={setIndex}
renderScene={SceneMap({
first: FirstRoute,
second: SecondRoute,
})}
/>
TabView Props
navigationState
(required
)
标签视图的状态。该状态应包含以下属性
index
:一个数字,表示routes
数组中活动路由的索引routes
:一个数组,包含用于渲染标签的路由对象列表
每个路由对象应包含以下属性
key
:用于标识路由的唯一键(必需)title
:要在标签栏中显示的路由标题icon
:要在标签栏中显示的路由图标accessibilityLabel
:标签按钮的辅助功能标签testID
:标签按钮的测试 ID
示例
{
index: 1,
routes: [
{ key: 'music', title: 'Music' },
{ key: 'albums', title: 'Albums' },
{ key: 'recents', title: 'Recents' },
{ key: 'purchased', title: 'Purchased' },
]
}
TabView
是一个受控组件,这意味着 index
需要通过 onIndexChange
回调进行更新。
onIndexChange
(required
)
在标签更改时调用的回调,接收新标签的索引作为参数。当调用它时,需要更新导航状态,否则更改将被丢弃。
renderScene
(required
)
回调,返回一个 React 元素以渲染为标签的页面。接收一个包含路由作为参数的对象
const renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'music':
return <MusicRoute jumpTo={jumpTo} />;
case 'albums':
return <AlbumsRoute jumpTo={jumpTo} />;
}
};
您需要确保您的各个路由实现 shouldComponentUpdate
以提高性能。为了更容易地指定组件,您可以使用 SceneMap
辅助函数。
SceneMap
接受一个对象,其中包含 route.key
到 React 组件的映射,并返回一个函数以与 renderScene
prop 一起使用。
import { SceneMap } from 'react-native-tab-view';
...
const renderScene = SceneMap({
music: MusicRoute,
albums: AlbumsRoute,
});
以这种方式指定组件更容易,并且负责实现 shouldComponentUpdate
方法。
每个场景接收以下 props
route
:组件渲染的当前路由jumpTo
:跳转到其他标签的方法,以route.key
作为其参数position
:表示当前位置的动画节点
jumpTo
方法可用于以编程方式导航到其他标签
props.jumpTo('albums');
使用 SceneMap
渲染的所有场景都使用 React.PureComponent
进行了优化,并且在父组件的 props 或状态更改时不会重新渲染。如果您需要更多地控制场景的更新方式(例如 - 即使 navigationState
没有更改也触发重新渲染),请直接使用 renderScene
而不是使用 SceneMap
。
重要提示: 不要 将内联函数传递给 SceneMap
,例如,不要执行以下操作
SceneMap({
first: () => <FirstRoute foo={props.foo} />,
second: SecondRoute,
});
始终在文件的顶层其他地方定义您的组件。如果您传递内联函数,它将在每次渲染时重新创建组件,这将导致整个路由在每次更改时卸载和重新挂载。这对性能非常不利,并且还会导致任何本地状态丢失。
如果您需要传递额外的 props,请使用自定义 renderScene
函数
const renderScene = ({ route }) => {
switch (route.key) {
case 'first':
return <FirstRoute foo={this.props.foo} />;
case 'second':
return <SecondRoute />;
default:
return null;
}
};
renderTabBar
回调,返回一个自定义 React 元素以用作标签栏
import { TabBar } from 'react-native-tab-view';
...
<TabView
renderTabBar={props => <TabBar {...props} />}
...
/>
如果未指定此项,则渲染默认标签栏。您传递此 props 以自定义默认标签栏,提供您自己的标签栏,或完全禁用标签栏。
<TabView
renderTabBar={() => null}
...
/>
tabBarPosition
标签视图中标签栏的位置。可能的值为 'top'
和 'bottom'
。默认为 'top'
。
lazy
函数,接受一个包含当前路由的对象,并返回一个布尔值,指示是否延迟渲染场景。
默认情况下,所有场景都将被渲染以提供更流畅的滑动体验。但是您可能希望推迟渲染未聚焦的场景,直到用户看到它们为止。要为特定场景启用延迟渲染,请从该 route
的 lazy
返回 true
<TabView
lazy={({ route }) => route.name === 'Albums'}
...
/>
当您为一个屏幕启用延迟渲染时,通常需要一些时间才能在它进入焦点时渲染。您可以使用 renderLazyPlaceholder
prop 来自定义用户在此短暂期间看到的内容。
您还可以传递一个布尔值来为所有场景启用延迟加载
<TabView lazy />
lazyPreloadDistance
当启用 lazy
时,您可以指定应预加载多少个相邻路由,通过此 prop。此值默认为 0
,这意味着延迟加载的页面会在它们进入视口时加载。
renderLazyPlaceholder
回调,返回一个自定义 React 元素,用于渲染尚未渲染的路由。接收一个包含路由作为参数的对象。还需要启用 lazy
prop。
此视图通常仅显示一瞬间。保持轻量。
默认情况下,这将渲染 null
。
keyboardDismissMode
字符串,指示是否在响应拖动手势时关闭键盘。可能的值为
'auto'
(默认):当索引更改时,键盘将被关闭。'on-drag'
:当拖动开始时,键盘将被关闭。'none'
:拖动不会关闭键盘。
swipeEnabled
布尔值,指示是否启用滑动 gestures。默认情况下启用滑动 gestures。传递 false
将禁用滑动 gestures,但用户仍然可以通过按标签栏来切换标签。
animationEnabled
在更改标签时启用动画。默认情况下为 true。
onSwipeStart
回调,在滑动 gesture 开始时调用,即用户触摸屏幕并移动它时。
onSwipeEnd
回调,在滑动 gesture 结束时调用,即用户在滑动 gesture 后将手指从屏幕上抬起时。
initialLayout
包含屏幕初始高度和宽度的对象。传递此项将提高初始渲染性能。对于大多数应用来说,这是一个很好的默认值
<TabView
initialLayout={{ width: Dimensions.get('window').width }}
...
/>
overScrollMode
用于覆盖 pager 的 overScroll 模式的默认值。可以是 auto
、always
或 never
(仅限 Android)。
pagerStyle
应用于包装所有场景的 pager 视图的样式。
style
应用于标签视图容器的样式。
TabBar
Material Design 主题标签栏。要自定义标签栏,您需要使用 TabView
的 renderTabBar
prop 来渲染 TabBar
并传递额外的 props。
例如,要自定义指示器颜色和标签栏背景颜色,您可以分别将 indicatorStyle
和 style
props 传递给 TabBar
const renderTabBar = props => (
<TabBar
{...props}
indicatorStyle={{ backgroundColor: 'white' }}
style={{ backgroundColor: 'pink' }}
/>
);
//...
return (
<TabView
renderTabBar={renderTabBar}
...
/>
);
TabBar Props
renderTabBarItem
函数,它接受一个 TabBarItemProps
对象,并返回一个自定义 React 元素,用作标签按钮。
renderIndicator
函数,它接受一个包含当前路由的对象,并返回一个自定义 React 元素,用作标签指示器。
onTabPress
在标签按下时执行的函数。它接收按下标签的场景,对于诸如滚动到顶部之类的事情很有用。
默认情况下,标签按下也会切换标签。要阻止此行为,您可以调用 preventDefault
<TabBar
onTabPress={({ route, preventDefault }) => {
if (route.key === 'home') {
preventDefault();
// Do something else
}
}}
...
/>
onTabLongPress
在标签长按时执行的函数,用于诸如显示具有更多选项的菜单之类的事情
activeColor
活动标签中图标和标签的自定义颜色。
inactiveColor
非活动标签中图标和标签的自定义颜色。
pressColor
Material ripple 的颜色(仅限 Android >= 5.0)。
pressOpacity
按下标签的透明度(仅限 iOS 和 Android < 5.0)。
scrollEnabled
布尔值,指示是否使标签栏可滚动。
如果您将 scrollEnabled
设置为 true
,您还应该在 tabStyle
中指定 width
以改善初始渲染。
bounces
布尔值,指示标签栏在滚动时是否反弹。
tabStyle
应用于标签栏中各个标签项的样式。
默认情况下,所有标签项都占用基于容器宽度预先计算的相同宽度。如果您希望它们采用原始宽度,您可以在 tabStyle
中指定 width: 'auto'
。
indicatorStyle
应用于活动指示器的样式。
indicatorContainerStyle
应用于指示器的容器视图的样式。
contentContainerStyle
应用于标签内部容器的样式。
style
(TabBar
)
应用于标签栏容器的样式。
gap
标签项之间的间距。
testID
(TabBar
)
标签栏的测试 ID。可用于在测试中滚动标签栏
选项
选项描述了应如何配置每个标签。有两种指定选项的方式
commonOptions
:应用于所有标签的选项。options
:应用于特定标签的选项。它以路由键作为键,并以包含选项的对象作为值。
示例
<TabView
commonOptions={{
icon: ({ route, focused, color }) => (
<Icon name={route.icon} color={color} />
),
}}
options={{
albums: {
labelText: 'Albums',
},
profile: {
labelText: 'Profile',
},
}}
/>
以下选项可用
accessibilityLabel
标签按钮的辅助功能标签。如果指定了 route.accessibilityLabel
,则默认使用它,否则使用路由标题。
accessible
是否将标签标记为 accessible
。默认为 true
。
testID
标签按钮的测试 ID。默认情况下使用 route.testID
。
labelText
标签按钮的标签文本。默认情况下使用 route.title
。
labelAllowFontScaling
标签字体是否应缩放以遵循“文本大小”辅助功能设置。默认为 true
。
href
用于 Web 上标签按钮的锚标记的 URL。
label
一个函数,返回一个自定义 React 元素以用作标签。该函数接收一个具有以下属性的对象
route
- 标签的路由对象。labelText
- 在labelText
选项或route title
中指定的标签文本。focused
- 标签是否处于聚焦状态。color
- 标签的颜色。allowFontScaling
- 标签字体是否应缩放以遵循“文本大小”辅助功能设置。style
- 标签的样式对象。
label: ({ route, labelText, focused, color }) => (
<Text style={{ color, margin: 8 }}>{labelText ?? route.name}</Text>
);
labelStyle
应用于标签项标签的样式。
icon
一个函数,返回一个自定义 React 元素以用作图标。该函数接收一个具有以下属性的对象
route
- 标签的路由对象。focused
- 图标是否处于聚焦状态。color
- 图标的颜色。size
- 图标的大小。
icon: ({ route, focused, color }) => (
<Icon name={focused ? 'albums' : 'albums-outlined'} color={color} />
);
badge
一个函数,返回一个自定义 React 元素以用作徽章。该函数接收一个具有以下属性的对象
route
- 标签的路由对象。
badge: ({ route }) => (
<View
style={{ backgroundColor: 'red', width: 20, height: 20, borderRadius: 10 }}
/>
);
sceneStyle
应用于包装每个屏幕的视图的样式。您可以传递此项以覆盖某些默认样式,例如溢出裁剪。
优化技巧
避免不必要的重新渲染
每次索引更改时都会调用 renderScene
函数。如果您的 renderScene
函数开销很大,则最好将每个路由移动到单独的组件,如果它们不依赖于索引,并在您的路由组件中使用 shouldComponentUpdate
或 React.memo
来防止不必要的重新渲染。
例如,代替
const renderScene = ({ route }) => {
switch (route.key) {
case 'home':
return (
<View style={styles.page}>
<Avatar />
<NewsFeed />
</View>
);
default:
return null;
}
};
执行以下操作
const renderScene = ({ route }) => {
switch (route.key) {
case 'home':
return <HomeComponent />;
default:
return null;
}
};
其中 <HomeComponent />
在您使用类组件时是一个 PureComponent
export default class HomeComponent extends React.PureComponent {
render() {
return (
<View style={styles.page}>
<Avatar />
<NewsFeed />
</View>
);
}
}
或者,如果您使用函数组件,则将其包装在 React.memo
中
function HomeComponent() {
return (
<View style={styles.page}>
<Avatar />
<NewsFeed />
</View>
);
}
export default React.memo(HomeComponent);
避免一帧延迟
我们需要测量容器的宽度,因此需要等待才能在屏幕上渲染某些元素。如果您预先知道初始宽度,则可以将其传入,我们将无需等待测量它。在大多数情况下,它只是窗口宽度。
例如,将以下 initialLayout
传递给 TabView
const initialLayout = {
height: 0,
width: Dimensions.get('window').width,
};
标签视图仍将对尺寸的变化做出反应,并进行相应的调整以适应诸如方向更改之类的情况。
优化大量路由
如果您有大量路由,尤其是图像,则可能会大大降低动画速度。您可以改为渲染有限数量的路由。
例如,执行以下操作以仅在每一侧渲染 2 个路由
const renderScene = ({ route }) => {
if (Math.abs(index - routes.indexOf(route)) > 2) {
return <View />;
}
return <MySceneComponent route={route} />;
};
避免在 ScrollView 中渲染 TabView
将 TabView
嵌套在垂直 ScrollView
中将禁用在 TabView
内部渲染的 FlatList
组件中的优化。因此,如果可能,请避免这样做。
使用 lazy
和 renderLazyPlaceholder
props 按需渲染路由
默认情况下禁用 lazy
选项以提供更流畅的标签切换体验,但您可以启用它并为占位符组件提供更好的延迟加载体验。启用 lazy
可以通过仅在路由进入视图时渲染它们来提高初始加载性能。有关更多详细信息,请参阅 prop 参考。