react-navigation笔记
安装依赖
npm install [email protected] [email protected] [email protected] [email protected] @react-native-community/[email protected] @react-navigation/[email protected]
@react-navigation/[email protected] @react-navigation/[email protected]
在根文件顶部引入,否则应用会崩溃
import 'react-native-gesture-handler';
将整个应用包装在 NavigationContainer
中
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return (
{/* Rest of your app code */}
);
}
安装 @react-navigation/stack
npm install @react-navigation/stack
createStackNavigator
是一个返回一个对象(包含2个属性 Screen
和 Navigator
)的函数。 Navigator
应该包含子元素 Screen
,来定义路由配置。用 NavigationContainer
包裹 Navigator
// In App.js in a new project
import * as React from 'react';
import { View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
function HomeScreen() {
return (
Home Screen
);
}
function DetailsScreen() {
return (
Details Screen
);
}
const Stack = createStackNavigator();
function App() {
return (
);
}
export default App;
配置
Screen
的必需配置属性是 name
和 component
。在 Navigator
上配置 initialRouteName
。
component
属性接受组件,不接受一个render函数。不要传一个内联函数例如 component={() =>
,否则当父组件re-render时,你的组件将卸载和重载失去的所有state。
每个路由的配置可以放在 options
里面。相同的可以配在 Navigator
的 screenOptions
里面。
传递额外的props:
- (推荐)使用React Context,用context provider包裹navigator去传数据到screens。
// Context lets us pass a value deep into the component tree
// without explicitly threading it through every component.
// Create a context for the current theme (with "light" as the default).
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// Use a Provider to pass the current theme to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing "dark" as the current value.
return (
);
}
}
// A component in the middle doesn't have to
// pass the theme down explicitly anymore.
function Toolbar() {
return (
);
}
class ThemedButton extends React.Component {
// Assign a contextType to read the current theme context.
// React will find the closest theme Provider above and use its value.
// In this example, the current theme is "dark".
static contextType = ThemeContext;
render() {
return ;
}
}
- 用render callback而不是定义一个component prop。默认情况下react-navigation底层做了优化防止重复渲染,但是使用render callback则会移除这些优化。如果用render callback,需要确保Screen组件使用了
React.memo
或React.PureComponent
{props => }
路由跳转
import * as React from 'react';
import { Button, View, Text } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
function HomeScreen({ navigation }) {
return (
Home Screen
);
}
// ... other code from the previous section
navigation
属性会传递给每个在stack navigator中定义的screen component。navigation.navigate('Detail')
调用navigate方法传入路由name,可以跳转到对应页面。navigation.push('Detail')
可以做到在Detail页面打开另一个Detail页面。navigation.goBack()
路由回退。-
回退多个页面的方式:
navigate('Home')
回退到Home页。navigation.popToTop()
会对到首页。
function DetailsScreen({ navigation }) {
return (
Details Screen
);
}
路由传参
步骤:
-
navigation.navigate('RouteName', { /* params go here */ })
,推荐使用JSON格式的参数。 - 在screen组件中读取传入的参数
route.params
。
function HomeScreen({ navigation }) {
return (
Home Screen
{
/* 1. Navigate to the Details route with params */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
);
}
function DetailsScreen({ route, navigation }) {
/* 2. Get the param */
const { itemId } = route.params;
const { otherParam } = route.params;
return (
Details Screen
itemId: {JSON.stringify(itemId)}
otherParam: {JSON.stringify(otherParam)}
navigation.push('Details', { itemId: Math.floor(Math.random() * 100) }}
/>
navigation.navigate('Home')} />
navigation.goBack()} />
);
}
更新路由参数: navigation.setParams
。
传参回到上一个页面: navigation.navigate('',params)
传参到嵌套的路由器:
navigation.navigate('Account', {
screen: 'Settings',
params: { user: 'jane' },
});
配置标题栏
设置标题:(在 Screen
上设置 options
)
options={{ title: 'My home' }}
-
options={({ navigation, route }) => ({ title: route.params.name })}
,options
函数的参数包括navigation
和route
,我们只需用到route
- 使用
navigation.setOptions({ title:'Updated!' })
更新options
修改header样式:(在 Screen
上设置 options
)
-
headerStyle
:参考View的样式写法 headerTintColor
-
headerTitleStyle
:参考Text的样式写法 - 注意:iOS上,status bar的文字和图标是黑色的,详见适配方案
复用常规options:
- 在
Stack.Navigator
上配置属性screenOptions
自定义组件替换标题: headerTitle
(默认是展示text的Text组件)
其他options:详见文档
function LogoTitle() {
return (
);
}
function StackScreen() {
return (
}}
/>
);
}
header 按钮
添加header按钮
- headerRight
function StackScreen() {
return (
,
headerRight: () => (
alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
);
}
header与它的screen组件交互
- 定义按钮时,在screen组件内使用
navigation.setOptions
而不是options
属性,这样可以获取screen的props、state、context等。
function StackScreen() {
return (
({
headerTitle: props => ,
})}
/>
);
}
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
setCount(c => c + 1)} title="Update count" />
),
});
}, [navigation]);
return Count: {count} ;
}
配置回退按钮
-
createStackNavigator
提供平台定制的默认回退按钮。 headerBackTitle
-
headerTruncatedBackTitle
:文档 -
headerBackImage
:文档
覆盖回退按钮
headerRight
headerLeft
- 保留button只覆盖
onPress
方法,可以从@react-navigation/stack
导出HeaderBackButton
组件分配给headerLeft
选项
嵌套导航器(todo)
在一个导航器的Screen内渲染另一个导航器。
-
Stack.Navigator
-
Home
(Tab. Navigator
)-
Feed
(Screen
) -
Messages
(Screen
)
-
-
Profile
(Screen
) -
Settings
(Screen
)
-
function Home() {
return (
);
}
function App() {
return (
);
}
注意:
- 每个嵌套导航器保持自己的导航历史
- 每个嵌套导航器有自己的
options
- 根据子导航器的状态设置父Screen的options
- 每个嵌套导航器有自己的参数
- 如果需要从父Screen传参到子Screen,可以用React Context透传参数给children
- 导航actions(比如
goBack
、navigate
)会被当前导航器处理,如果处理不了则会冒泡(被父导航器)处理 - 导航器的特定方法在嵌套导航器中也可用:
- 如果需要从父导航器dispatch actions到嵌套的子导航器,可以使用
navigation.dispatch
- 如果需要从父导航器dispatch actions到嵌套的子导航器,可以使用
// 从父导航器dispatch actions到嵌套的子导航器
navigation.dispatch(DrawerActions.toggleDrawer());
{/** Drawer的openDrawer方法可以被Stack调用(navigation.openDrawer) */}
{/** ... */}
{/** Drawer的openDrawer方法不可被Stack下的其他Screen调用 */}
{/** ... */}
{/** Tab可以调用Stack的方法push和replace(navigation.push、navigation.replace) */}
{/** ... */}
嵌套导航器不接收父导航器的事件,如需接受父导航器的事件,需要使用 navigation.dangerouslyGetParent()
显式监听
const unsubscribe = navigation
.dangerouslyGetParent()
.addListener('tabPress', (e) => {
// Do something
});
父导航器的UI渲染在子导航器的最顶端:
- Drawer的每一个页面中嵌套Stack:抽屉出现在堆栈标题的上方
- Stack中嵌套Drawer:抽屉显式在标题下方
- Stack的首页中嵌套Tab:新页面会覆盖掉tab bar
- Tab的每一个页面中嵌套Stack:tab bar一直显示,再按一下tab会回到stack的顶部
在嵌套导航器中跳转页面
-
Drawer.Navigator
-
Drawer.Screen
:Home
-
Drawer.Screen
:Root
-
Stack.Screen
:Profile
-
Stack.Screen
:Settings
-
-
navigation.navigate('Root');
navigation.navigate('Root', { screen: 'Settings' });
在以前的版本中,所有配置都是静态的,但是现在通过动态配置,直到包含Screen的导航器呈现之前,react-navigation才知道哪些Screen可用以及在哪里可用。通常,Screen在导航进入之前不会渲染任何东西,因此尚未渲染的导航器配置不可用。这使得必须制定要导航到的层次结构。这也是为什么应该尽可能减少嵌套导航器以使代码更简单的原因。
将参数传递给嵌套导航器中Screen
// 指定params传递参数
navigation.navigate('Root', {
screen: 'Settings',
params: {
user: 'jane'
},
});
// 深层嵌套
navigation.navigate('Root', {
screen: 'Settings',
params: {
screen: 'Sound',
params: {
screen: 'Media',
},
},
});
渲染定义在导航器中的初始路由
默认情况下,在嵌套导航器中导航Screen时,指定的screen会被用作初始screen,并且导航器上的initialRoute prop会被忽略。
如果需渲染特定的初始路由页面,可以设置 initial: false
。
navigation.navigate('Root', {
screen: 'Settings',
initial: false,
});
嵌套多个stack 导航器
当嵌套多个Stack导航器时,react-navigation将自动隐藏子stack导航器的标题,以避免重复的标题。但是根据场景不同,显示子stack导航器的标题,而不是隐藏父stack导航器的标题,可能更有用。可以设置 headerShown: false
解决该问题。完整示例 极少的情况,你需要同事显示父stack导航器和子stack导航器的标题,可以在子导航器上设置 headerShown: true
function Home() {
return (
);
}
function App() {
return (
);
}
嵌套的最佳做法:建议将嵌套导航器减少到最小。尝试通过尽可能少的嵌套来实现所需的行为。缺点如下
- 会导致深度嵌套的视图层级结构,从而可能导致低端设备出现内存和性能问题
- 嵌套相同类型的导航器可能会导致混淆的用户体验
- 如果嵌套过多,导航到嵌套页面,配置深层链接等时将很难编写代码
导航生命周期
堆栈导航器中包含A和B两个screen。当导航到A页面时,A的 componentDidMount
被调用,当跳转到B时,B的 componentDidMount
被调用,但是A在stack中保持mounted,A的 componentWillUnmount
没有被调用。
当从B回退到A时,B的 componentWillUnmmount
被调用,但是A的 componentDidMount
没有被调用,因为A一直是mounted状态。
生命周期事件文档
focus
blur
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// Screen was focused
// Do something
});
return unsubscribe;
}, [navigation]);
return ;
}
- 代替手动添加事件监听,可以用
useFocusEffect
hook去执行副作用,类似react的useEffect
hook。 - 使用
useIsFocused
hook:返回boolean,表示屏幕是否聚焦
import { useFocusEffect } from '@react-navigation/native';
function Profile() {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
};
}, [])
);
return ;
}
打开全屏modal(需要嵌套导航器知识)
在Navigator上设置属性 mode="modal"
,调用 navigation.navigate
打开modal
function HomeScreen({ navigation }) {
return (
This is the home screen!
navigation.navigate('MyModal')}
title="Open Modal"
/>
);
}
function DetailsScreen() {
return (
Details
);
}
function ModalScreen({ navigation }) {
return (
This is a modal!
navigation.goBack()} title="Dismiss" />
);
}
const MainStack = createStackNavigator();
const RootStack = createStackNavigator();
function MainStackScreen() {
return (
);
}
function RootStackScreen() {
return (
);
}
注意:
-
mode
可以设置为card
(默认)和modal
(iOS:从底部划出,从顶部向下滑动以关闭;安卓:无效) - 当调用
navigate
时,除了路由外,无需指定其他任何内容。(react-navigation尝试在最近的导航器上查找路由,然后在该位置执行操作)
Tab Navigator(todo)
文档
术语表
header: 屏幕顶端的矩形,包含回退按钮和标题
Navigator
:包含子元素 Screen
。NavigationContainer是一个管理导航树和包含导航状态的组件。这个组件必须包着所有的导航结构。通常我们在app的顶部(通常在App.js里)渲染这个组件。
function App() {
return (
// <---- This is a Navigator
);
}
Screen 组件:定义路由配置的组件
被配置在路由配置里的Screen组件(被 navigation.navigate
唤起的Screen)被提供了 navigation
props,
const Stack = createStackNavigator();
const StackNavigator = (
);
Navigation prop navigation prop文档
dispatch
navigate
goBack
- ...
Route proproute prop文档
params
key
name
Navigation state:结构如下,index指向当前的路由(B)
{
key: 'StackRouterRoot',
index: 1,
routes: [
{ key: 'A', name: 'Home' },
{ key: 'B', name: 'Profile' },
]
}
Route
{
key: 'B',
name: 'Profile',
params: { id: '123' }
}
兼容性层:降低升级的代码改动成本
- 安装
@react-navigation/compat
:
npm install @react-navigation/native @react-navigation/compat @react-navigation/stack
- 使用
createCompatNavigatorFactory
包裹
-import { createStackNavigator } from 'react-navigation-stack';
+import { createStackNavigator } from '@react-navigation/stack';
+import { createCompatNavigatorFactory } from '@react-navigation/compat';
-const RootStack = createStackNavigator(
+const RootStack = createCompatNavigatorFactory(createStackNavigator)(
{
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
},
{
initialRouteName: 'Profile',
}
);
-
NavigationActions
改为从@react-navigation/compat
中导出
-import { NavigationActions } from 'react-navigation';
+import { NavigationActions } from '@react-navigation/compat';
-
@react-navigation/compat
导出的api包括:- Actions:
NavigationActions
StackActions
DrawerActions
SwitchActions
- HOCs
withNavigation
withNavigationFocus
- Navigators
createSwitchNavigator
- Compatibility helpers
-
createCompatNavigatorFactory
- 传入一个使用v5的api的导航器,返回一个使用v4 api的createXNavigator
-
createCompatNavigationProp
- 传入一个v5 的navigation
对象 和route对象,返回一个v4的navigation
对象
-
- Actions:
-
兼容层处理了:
- 使用v4的静态配置api,俄日不是基于api的组件
- 改变了
navigation
对象上的方法名称去适配v4 - 添加了对
screenProps
的支持(v5上被移除) - 导出了跟v4同名的action creators,例如
NavigationActions
、StackActions
、SwitchActions
。
-
兼容层没处理:v5动态api导致v4静态api不再具有某些功能
- 不再包含navigator的props或者options。意味着你要传递给navigator的options可能因为重大改变而不同。
- 通过在路由配置中定义path来定义旧式深层链接是不支持的
- 导航到导航器的工作原理不同,我们无法导航到尚未渲染的导航器中的屏幕,并且参数不会合并到所有子屏幕。
- 某些采取一系列操作的方法例如
reset
将不再支持。 - 不导出
createAppContainer
,需要对容器使用v5的apiNavigationContainer
。 - 如果使用的高阶api例如Redux集成、自定义路由器、actions,这些不再支持,并且你需要移除Redux集成。
-
为什么用:
- 允许使用新api写代码,同时使用旧api与代码集成
- 建立在支持TypeScript的v5之上,旧代码也可以利用类型检查功能,对重构有帮助
- 可以在旧组件中访问新api,例如
navigation.setOptions
或者一些新的hook例如useFocusEffect
。
在没用navigation props时navigate
有时你需要从一个没有
navigation
prop的地方触发navigation action,比如redux middleware,在这种时候,你可以从navigation容器中dispatch navigation actions。如果你想要找一种方式,从一个不需要传
navigation
prop下去的组件里面navigate,可以用useNavigation
。当你可以拿到navigation prop或者useNavigation
时不要用这个方法,因为它的行为会有所不同,而且许多特定于屏幕的helper方法将不可用。你可以通过
ref
访问根导航器对象,将其传递给RootNavigation
,我们稍后将使用该RootNavigation
进行导航。
// App.js
import { NavigationContainer } from '@react-navigation/native';
import { navigationRef } from './RootNavigation';
export default function App() {
return (
{/* ... */}
);
}