本文章目前使用的是最新版的React-Native0.72.7
Node.js >= 16
JDK = 11
// package.json
"dependencies": {
"@react-navigation/native": "^6.1.9",
"@react-navigation/native-stack": "^6.9.17",
"react": "18.2.0",
"react-native": "0.72.7",
"react-native-safe-area-context": "^4.7.4",
"react-native-screens": "^3.27.0"
}
安装 react-navigation/native
yarn add @react-navigation/native
安装 react-native-screens和react-native-safe-area-context
yarn add react-native-screens react-native-safe-area-context
react-native-screens需要额外的配置步骤才能在Android设备上正常工作,找到项目下android/app/src/main/java/<your package name>/MainActivity.java
将下面的代码添加到MainActivity类的主体中
public class MainActivity extends ReactActivity {
// ...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
// ...
}
在MainActivity.java文件顶部引入
import android.os.Bundle;
我们需要将整个应用程序封装在NavigationContainer中。通常你会在你的入口文件中这样做,比如index.js或App.js
// App.tsx
import { NavigationContainer } from '@react-navigation/native'
export default function App() {
return (
<NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
)
}
在典型的React Native应用程序中,NavigationContainer应该只在根应用程序中使用一次。除非有特定的用例,否则不应该嵌套多个NavigationContainers。
安装@react-navigation/native-stack
Native Stack 使用 Android 和 IOS 原生导航系统在页面之间导航
yarn add @react-navigation/native-stack
createNativeStackNavigator是一个函数,它返回一个包含两个属性的对象:Screen和Navigator。它们都是用于配置 导航器(navigator) 的React组件。导航器应包含Screen元素作为其子元素,以定义路线的配置。
NavigationContainer是一个管理导航树并包含导航状态的组件。此组件必须包装所有导航器结构。通常,我们会在应用程序的根目录下呈现这个组件,通常是从app.js导出的组件。
// App.tsx
import { View, Text } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
)
}
const Stack = createNativeStackNavigator()
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
@react-navigation/native-stack需要依赖我们刚刚装过的那些库
以上图片是运行后的界面
导航栏和内容区域的样式是堆栈导航器stack navigator的默认配置
路由名称的大小写无关紧要——您可以使用小写的home或大写的home,这取决于您自己。我们更喜欢将路线名称大写。
首屏渲染 initialRouteName
让我们在本机堆栈导航器中添加第二个屏幕,并将主屏幕配置为首先渲染
// App.tsx
function HomeScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
)
}
function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
现在,我们的堆栈有两个路由,一个Home路由和一个Details路由。可以使用Screen组件指定路线。Screen组件接受一个与我们将用于导航的路线名称相对应的名称prop,以及一个与它将渲染的组件相对应的组件prop。
使用initialRouteName属性设置首屏渲染
这里,Home路由对应于HomeScreen组件,Details路由对应于DetailsScreen组件。堆栈的初始路由是Home路由。尝试将其更改为Details并重新加载应用程序
Stack.Screen里面的component接受组件,而不是渲染函数。不要传递内联函数(例如component={()=><HomeScreen/>}),否则当父组件重新渲染时,您的组件将卸载并重新装载,失去所有状态。
React Native的快速刷新不会像您所期望的那样更新initialRouteName中的更改,请注意,您现在将看到Details屏幕。然后将其更改回“主页”并再次重新加载。
Stack.Screen的options属性
导航器中的每个屏幕都可以为导航器指定一些选项,例如要在标题中呈现的标题。这些选项可以在每个屏幕组件的选项道具中传递
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: '我是Home的title' }}
/>
以上图片蓝色圈中的文字就是options中传递的title
Stack.Screen自定义传递数据
const someData = [{ name: 'zhangs' }, { name: '李四' }]
const HomeScreen = ({ navigation, extraData }) => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Home Screen</Text>
<Text>我是extraData{JSON.stringify(extraData)}</Text>
</View>
)
}
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home' options={{ title: '我是Home的title' }}>
{/* extraData自定义数据 */}
{props => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>
<Stack.Screen name='Details' component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
以上蓝色圈中是传递的数据
Stack.Navigator 是一个组件,它将路由配置作为其子级,并为配置提供额外的道具,并呈现我们的内容。
Stack.Screen组件采用一个name的prop和component prop
name prop引用路由的名称
component prop指定要为路由渲染的组件
这是两个必需的prop
navigation.push 有历史记录的跳转
<Button
title="我是详情页的按钮"
onPress={() => navigation.push('Details')}
/>
如果在Details里面使用 navigation.navigate(‘Details’) 的话他不会在继续跳转,因为已经在当前页面了
在Details里面使用 navigation.push(‘Details’) 这种写法就可以在Details里面继续跳转详情页
navigation.goBack 返回
从Home页面里面跳转到Details里面返回的时候可用 navigation.goBack方法返回
// App.tsx
const DetailsScreen = ({ navigation }) => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Details Screen</Text>
<Button title='返回goBack()' onPress={() => navigation.goBack()} />
</View>
)
如果已经在第一个页面了,而且堆栈里面也没有其他路由的话使用此方法会报错(这只是一个仅用于开发的警告,不会在生产中显示。)
错误提示如下
navigation.popToTop 回到第一个页面
如果您在一个堆栈中有几个屏幕,并且希望忽略所有屏幕返回第一个屏幕。在这种情况下,我们知道我们想回到主页,这样我们就可以使用navigation.navigate(‘Home’),这里还有一个方法可以在堆栈中的屏幕直接返回第一个屏幕,使用 navigation.popToTop方法即可
// App.tsx
const DetailsScreen = ({ navigation }) => (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Details Screen</Text>
<Button title='回到第一个页面' onPress={() => navigation.popToTop()} />
</View>
)
params 路由传参
navigation.push(跳转的路径, 携带的参数),navigation.navigate亦是如此
在Detais1里面接收参数的时候,先在参数里面结构出来route,route里面有一个params对象,params对象里面就是Home1里面带过来的参数,建议传递的params是JSON可序列化的
// App.tsx
import { View, Text, Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
const someData = [{ name: 'zhangs' }, { name: '李四' }]
const HomeScreen1 = ({ navigation }) => (
<View>
<Text>我是HomeScreen1</Text>
<Button title='params带参数传参' onPress={() => navigation.push('Details1', someData)} />
</View>
)
const DetailsScreen1 = ({ route }) => {
const params = route.params || {}
return (
<View>
<Text>我是DetailsScreen1</Text>
<Text>我是接收到的参数:{JSON.stringify(params)}</Text>
</View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home1' component={HomeScreen1} />
<Stack.Screen name='Details1' component={DetailsScreen1} />
</Stack.Navigator>
</NavigationContainer>
)
}
initialParams 初始参数
您可以将一些初始参数传递到screen。如果导航到此screen时没有指定任何参数,则将使用初始参数。它们也与您传递的任何参数浅合并。可以使用initialParams指定初始参数
// App.tsx
import { View, Text, Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
const HomeScreen1 = ({ navigation }) => (
<View>
<Text>我是HomeScreen1</Text>
<Button title='不带参数跳转' onPress={() => navigation.push('Details1')} />
</View>
)
const DetailsScreen1 = ({ route }) => {
const params = route.params
return (
<View>
<Text>我是DetailsScreen1</Text>
<Text>我是默认参数:{JSON.stringify(params)}</Text>
</View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home1' component={HomeScreen1} />
<Stack.Screen name='Details1' initialParams={[{name: '小明'}]} component={DetailsScreen1} />
</Stack.Navigator>
</NavigationContainer>
)
}
navigation.setParams 修改路由传递的参数
Screens也可以更新参数,就像跟新state一样,navigation.setParams方法用于更新Screens的参数
// App.tsx
import { View, Text, Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
const HomeScreen1 = ({ navigation }) => (
<View>
<Text>我是HomeScreen1</Text>
<Button title='不带参数跳转' onPress={() => navigation.push('Details1')} />
</View>
)
const DetailsScreen1 = ({ route }) => {
const params = route.params
return (
<View>
<Text>我是DetailsScreen1</Text>
<Text>我是默认参数:{JSON.stringify(params)}</Text>
<Button title='修改params参数' onPress={() => navigation.setParams({ name: '小明修改' })} />
</View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home1' component={HomeScreen1} />
<Stack.Screen name='Details1' initialParams={[{name: '小明'}]} component={DetailsScreen1} />
</Stack.Navigator>
</NavigationContainer>
)
}
避免使用setParams更新标题等屏幕选项。如果需要更新选项,请使用setOptions。(这是官方的注释)
将 params 传递到上个 screen
参数不仅对将某些数据传递到新screen有用,而且对将数据传递到前一个screen也有用。
为了实现这一点,您可以使用navigate方法,如果 screen已经存在,该方法的作用类似于goBack。 您可以通过navigation传递params以将数据传递回
import { useState, useEffect } from 'react'
import { View, Text, Button, TextInput } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
const HomeScreen = ({ navigation, route }) => {
useEffect(() => {
// 每次这里都会发生变化
console.log(route.params)
}, [route.params?.postText])
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Home Screen</Text>
<Text>我是postText:{route.params?.postText}</Text>
<Button title='跳转详情' onPress={() => navigation.navigate('Details')} />
</View>
)
}
const DetailsScreen = ({ route, navigation }) => {
const [postText, setPostText] = useState()
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Details Screen</Text>
<TextInput
placeholder='请输入'
style={{ height: 50, width: '90%', padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title='返回携带参数'
onPress={() => {
navigation.navigate({
name: 'Home',
params: { postText },
merge: true
})
}}
/>
</View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home' component={HomeScreen} />
<Stack.Screen name='Details' initialParams={{ name: '小明', age: 18 }} component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
merge 表示合并参数,如果不设置该参数或者为false,则原来的参数会被替换,而不是合并,请注意!
例如:
- About页面跳转到Home页面的时候路由的params里面携带了一个name字段
- 在Home页面中使用route.params.name显示出来
- 在从Home页面跳转到Details页面
- 在Details页面里面再次跳转到上个Home页面的时候路由params携带了一个postText字段
4.1 此时merge字段为空或者设置为false的时候,再次回到Home页面第一次从About页面携带的name字段就没了,因为已经Details页面携带的字段替换掉了
4.2 如果设置merage字段为true的话那么再次回到Home页面的时候那个从About页面携带的name字段就不会被替换,而是合并
合并后的params就是 params: {name: ‘我是从About页面携带的参数’, postText: ‘我是从Details页面携带的参数’}
params的变更可以通过监听 navigation的state变化获取
在标题中使用params(options作为函数的使用)
options可以写成一个函数,如果我们将选项作为一个函数,那么React Navigation将用一个包含{Navigation,route}的对象来调用它
// App.tsx
import { useState} from 'react'
import { View, Text, Button, TextInput } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
export default function App() {
const HomeScreen = ({ navigation, route }) =>
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Home Screen</Text>
<Text>我是postText:{route.params?.postText}</Text>
<Button title='跳转详情' onPress={() => navigation.navigate('Details')} />
</View>
)
}
const DetailsScreen = ({ route, navigation }) => {
const [postText, setPostText] = useState()
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>我是Details Screen</Text>
<TextInput
placeholder='请输入'
style={{ height: 50, width: '90%', padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title='返回携带参数'
onPress={() => {
navigation.navigate({
name: 'Home',
params: { postText },
merge: true
})
}}
/>
</View>
)
}
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
// 注意这里的options是一个箭头函数
<Stack.Screen name='Home' component={HomeScreen} options={({route}) => ({title: route.params.postText})} />
<Stack.Screen name='Details' initialParams={{ name: '小明', age: 18 }} component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
- 上图中Home页面默认的title就是Home
- 跳转到Details页面中在输入框写入文字,并且携带参数跳转到Home页面
- 在Stack.Screen中设置了options并且为函数,在其中把route.params中的postText赋值给了title
- 再次到Home页面的时候看头部就是路由里面携带的postText字段了
传递给options函数的参数是一个具有以下属性的对象:
navigation-屏幕的导航道具。
route-屏幕的路由道具
在这里我们只需要上面例子中的route prop,但在某些情况下,您可能也想使用navigation。
setOptions 更新选项(header styles)
在自定义页眉样式时,有三个关键属性可供使用:headerStyle、headerIntColor和headerTitleStyle。
// App.tsx
import { View, Text, Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
const HomeScreen = ({ navigation }) => (
<View>
<Text>HomeScreen</Text>
</View>
)
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
headerStyle: {
backgroundColor: '#f4511e'
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
headerStyle:将应用于包装页眉的视图的样式对象。如果你在上面设置backgroundColor,那将是你的标题的颜色。
headerIntColor:后退按钮和标题都使用此属性作为颜色。在下面的示例中,我们将色调设置为白色(#fff),因此后退按钮和标题将为白色。
headerTitleStyle:如果我们想自定义标题的fontFamily、fontWeight和其他Text样式属性,我们可以使用它。
screenOptions(跨屏幕共享常用选项)
上面的例子中您会注意到,当您导航到DetailsScreen时,颜色会返回到默认值。如果我们必须将选项标题样式的属性从HomeScreen复制到DetailsScreen,以及我们在应用程序中使用的每个屏幕组件,那不是很糟糕吗?
我们可以将配置写在Stack.Navigator screenOptions中。
// App.tsx
import { View, Text, Button } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
const HomeScreen = ({ navigation }) => (
<View>
<Text>HomeScreen</Text>
<Button title='跳转到详情页' onPress={() => navigation.navigate('Details')} />
</View>
)
const DetailsScreen = () => (
<View>
<Text>DetailsScreen</Text>
</View>
)
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName='Home'
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e'
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold'
}
}}
>
<Stack.Screen name='Home' component={HomeScreen} />
<Stack.Screen name='Details' component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
现在我们所有的screen都是这种配置的颜色了
Stack.Screen custom component(自定义组件)
有时,您需要更多的控制权,而不仅仅是更改标题的文本和样式——例如,您可能需要渲染一个图像来代替标题,或者将标题制作成一个按钮。在这些情况下,您可以完全覆盖用于标题的组件,并提供自己的组件。
// App.tsx
import { View, Text, Button, Image } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
// 使用自定义组件替代title
const LogoTitle = () => (
<Image
style={{ width: 50, height: 50 }}
source={{ uri: 'https://ts1.cn.mm.bing.net/th?id=OIP-C.Zte3ljd4g6kqrWWyg-8fhAHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&pid=3.1&rm=2' }}
/>
)
const HomeScreen = ({ navigation }) => (
<View>
<Text>HomeScreen</Text>
<Button title='跳转到详情页' onPress={() => navigation.navigate('Details')} />
</View>
)
const DetailsScreen = () => (
<View>
<Text>DetailsScreen</Text>
</View>
)
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home' screenOptions={{ headerTitle: props => <LogoTitle {...props} /> }}>
<Stack.Screen name='Home' component={HomeScreen} />
<Stack.Screen name='Details' component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
当我们提供组件而不是标题时,为什么要像以前那样使用headerTitle?原因是headerTitle是一个特定于页眉的属性,而title也将用于选项卡栏、抽屉等。
headerTitle默认为显示标题的Text组件。
options可以是一个对象或一个函数。当它是一个函数时,它会被提供一个带有navigation和route的对象。
此链接可查看native stack navigator全部options完整列表
options中的headerRight
与标题交互的最常见方式是点击标题左侧或右侧的按钮。让我们在标题的右侧添加一个按钮
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
)
}}
/>
</Stack.Navigator>
header与其screen component的交互
在某些情况下,标头中的组件需要与屏幕组件交互。对于这个用例,我们需要使用navigation.setOptions来更新我们的选项。通过在屏幕组件中使用navigation.setOptions,我们可以访问屏幕的props, state, context等
import { useEffect, useState } from 'react'
import { View, Text, Button, Image } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
const HomeScreen = ({ navigation }) => {
const [count, setCount] = useState(0)
useEffect(() => {
navigation.setOptions({
headerRight: () => <Button title='更新值' onPress={() => setCount(c => c + 1)} />
})
}, [navigation])
return <Text>count:{count}</Text>
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName='Home'>
<Stack.Screen name='Home' component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
嵌套导航(Nesting navigators)
嵌套导航器意味着在另一个导航器的屏幕内呈现导航器,也就是二级路由
待定。。。