1.开始
步骤一:安装基础库
npm install @react-navigation/native
步骤二:安装相关的依赖
npm install react-native-screens react-native-safe-area-context
iOS:
npx pod-install
注意:
从React Native 0.60及以上,会自动链接。所以不需要 run react-native link.
ios 如果不成功可以cd ios目录 执行pod install
android:
在目录android/app/src/main/java//MainActivity.java下引入以下代码:
import android.os.Bundle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(null);
}
步骤三:写入口代码
在入口引入NavigationContainer组件
import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return (
<NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
);
}
注意:NavigationContainer不要在项目里写多个
2.堆栈导航器
步骤一:安装
npm install @react-navigation/native-stack
步骤二:代码
import * as React from 'react';
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>
);
}
function DetailsScreen() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>Details Screen</Text>
</View>
);
}
// 创建一个堆栈导航器
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
{/* initialRouteName 指定初始化的路由 */}
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{title: '首页'}}
/>
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
/**
* Stack.Screen
* name 是路由的名字
* component 是要渲染的组件
* option里面是一些针对这个屏幕的配置 如:title 是显示在header上的标题
* 另外:screenOptions可以用来配置所有屏幕的option,也就是全局配置
*/
步骤三:怎么传递props
- React context
- 回调函数
回调函数的实现?
<Stack.Screen name="Home">
{(props) => <HomeScreen {...props} extraData={someData} />}
</Stack.Screen>
注意:
- 默认情况下,React Navigation会对屏幕组件进行优化,以防止不必要的渲染。使用渲染回调可以消除这些优化。如果你使用渲染回调,你需要确保你使用 React.memo 或者React.PureComponent,以避免性能问题。
- 每次改了路由,需要重新运行项目,reload是没有用的。
3.屏幕之间跳转
方法一:navigation.navigate
import * as React from 'react';
import {Button, View, Text} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
function HomeScreen({navigation}: any): React.ReactElement {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate('Details')}
/>
</View>
);
}
function DetailsScreen(): React.ReactElement {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>Details Screen</Text>
</View>
);
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
{/* initialRouteName 指定初始化的路由 */}
<Stack.Navigator initialRouteName="Home">
<Stack.Screen
name="Home"
component={HomeScreen}
options={{title: '首页'}}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{title: '详情'}}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
注意:navigation.navigate跳转的页面只能是不同路由,如果路由一样比如在Details路由里面navigation.navigate(’ Details’)是没有效果的
方法二:navigation.push
使用navigation.push可以在相同的路由间跳转
<Button
title="Go to Details... again"
onPress={() => navigation.push('Details')}
/>
方法三:返回
navigation.goBack()
用于返回上一页
navigation.navigate(‘Home’)
当我们push了很多页,但是想回到第一页的时候,并且我们知道第一页的路由是Home,可以用navigation.navigate
navigation.popToTop()
直接放回到最顶层的第一页
在Android上,React Navigation挂钩到硬件返回按钮,并在用户按下它时为您触发goBack()函数,因此它的行为与用户预期的一样。
4.向路由传递参数
参数的传递和获取
navigate和push都可以
function HomeScreen({navigation}: any): React.ReactElement {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>Home Screen</Text>
<Button
title="Go to Details"
onPress={() => {
/* 把路由的参数通过对象的形式传递 */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
/>
</View>
);
}
function DetailsScreen({route, navigation}: any): React.ReactElement {
// 获取路由传递的参数
const {itemId, otherParam} = route.params;
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Go to Details... again"
onPress={() =>
navigation.push('Details', {
itemId: Math.floor(Math.random() * 100),
})
}
/>
<Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
<Button title="Go back" onPress={() => navigation.goBack()} />
</View>
);
}
更新屏幕的参数
navigation.setParams({
query: 'someText',
});
注意:避免使用setParams来更新屏幕选项,如title等。如果需要更新选项,请使用setOptions。
设置路由初始参数
通过initialParams来指定。
如果在导航到此屏幕时没有指定任何参数,则将使用初始参数。它们也与你传递的任何params浅合并。并且就算是返回上一个界面传参merge:false,也就是替换parmas,只会替换我们切换路由传的的参数。initialParams指定的参数始终会在
<Stack.Screen
name="Details"
component={DetailsScreen}
initialParams={{ itemId: 42 }}
/>
将参数传递到前一个屏幕
function HomeScreen({ navigation, route }) {
React.useEffect(() => {
if (route.params?.post) {
// Post updated, do something with `route.params.post`
// For example, send the post to the server
}
}, [route.params?.post]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button
title="Create post"
onPress={() => navigation.navigate('CreatePost')}
/>
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</View>
);
}
function CreatePostScreen({ navigation, route }) {
const [postText, setPostText] = React.useState('');
return (
<>
<TextInput
multiline
placeholder="What's on your mind?"
style={{ height: 200, padding: 10, backgroundColor: 'white' }}
value={postText}
onChangeText={setPostText}
/>
<Button
title="Done"
onPress={() => {
// 传递和合并参数回主屏幕
navigation.navigate({
name: 'Home',
params: { post: postText },
// merge:false 代表替换, merge:true代表合并
merge: true,
});
}}
/>
</>
);
}
注意:initialParams指定的参数就算是merge:false也会存在
navigation.navigate({
name: 'Home',
params: { post: postText },
});
// 当没有home页面的时候是跳转新页面,当存在home页面时是返回到上一个路由栈里面最近的home页面
嵌套的屏幕传递参数
例如,假设您在Account屏幕中有一个导航器,并希望将参数传递到该导航器中的Settings屏幕。然后你可以像下面这样传递参数:
navigation.navigate('Account', {
screen: 'Settings',
params: { user: 'jane' },
});
params里面应该有什么?
它们应该只包含配置屏幕上显示的内容的信息。避免传递将显示在屏幕本身的完整数据(例如传递用户id而不是用户对象)。还要避免传递被多个屏幕使用的数据,这样的数据应该在全局存储中。
您也可以将路由对象看作URL。如果屏幕上有一个URL, URL中应该包含什么?Params不应该包含您认为不应该出现在URL中的数据。这通常意味着你应该保留尽可能少的数据来确定屏幕是什么。想象一下访问一个购物网站,当你看到产品列表时,URL通常包含类别名称,排序类型,任何过滤器等,它不包含屏幕上显示的实际产品列表。
通过深度链接或在Web上链接到屏幕也会出现问题,因为:
- URL是屏幕的表示,因此它还需要包含参数,即完整的用户对象,这可能会使URL非常长和不可读
- 由于用户对象位于URL中,所以可以传递一个表示不存在的用户或配置文件中数据不正确的用户的随机用户对象
- 如果用户对象没有被传递,或者格式不正确,这可能会导致崩溃,因为屏幕不知道如何处理它
// 好的params参数
navigation.navigate('Profile', { userId: 'jane' });
5.配置标题栏
设置头部标题
通过option的title
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My home' }}
/>
在标题中使用parmas
options作为一个函数,option的参数有两个,一个navigation,一个route
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My home' }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({ title: route.params.name })}
/>
</Stack.Navigator>
);
}
通过setOptions更新options
/* Inside of render() of React class */
<Button
title="Update the title"
onPress={() => navigation.setOptions({ title: 'Updated!' })}
/>
调整头部的样式
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
title: 'My home',
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
</Stack.Navigator>
);
}
主要是设置:headerStyle, headerTintColor, headerTitleStyle
设置所有屏幕的配置项
screenOptions是所有屏幕的配置项
function StackScreen() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My home' }}
/>
</Stack.Navigator>
);
}
用自定义组件替换标题
有时候你需要更多的控制,而不仅仅是改变标题的文本和样式—-例如,你可能想渲染一个图像来代替标题,或者将标题变成一个按钮。在这些情况下,您可以完全覆盖用于标题的组件,并提供您自己的组件。
function LogoTitle() {
return (
<Image
style={{ width: 50, height: 50 }}
source={require('@expo/snack-static/react-native-logo.png')}
/>
);
}
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ headerTitle: (props) => <LogoTitle {...props} /> }}
/>
</Stack.Navigator>
);
}
您可能想知道,为什么在提供组件时使用headerTitle,而不是像以前那样提供title ?原因是headerTitle是一个特定于堆栈导航器的属性,headerTitle默认为显示标题的Text组件。
headerTitle的props里面是children和tintColor
children的值是options里面title设置的值,
<Stack.Screen
name="CreatePost"
component={CreatePostScreen}
// 接收动态路由的值 children里面是title的值,传递给组件DetailsTitle
options={({route}: any) => ({
title: route.params.a,
headerTitle: ({children}) => <DetailsTitle children={children} />,
})}
/>
function DetailsTitle(props: any): React.ReactElement {
return (
<Text style={{backgroundColor: '#999900'}}>
{props.children ? props.children : '我是默认标题'}
</Text>
);
}
6.头部按钮
头部添加一个button
在头部的右边添加一个button
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerTitle: (props) => <LogoTitle {...props} />,
headerRight: () => (
<Button
onPress={() => alert('This is a button!')}
title="Info"
color="#fff"
/>
),
}}
/>
</Stack.Navigator>
);
}
头部右边通过headerRight来自定义。但是上面的代码还有问题,options里面的this不是HomeScreen的实例,所以你不能在它上调用setState或任何实例方法。
头部与屏幕组件的交互
为了能够与屏幕组件交互,我们需要使用导航。setOptions来定义我们的按钮,而不是options prop。通过使用导航。setOptions在屏幕组件中,我们可以访问屏幕的道具,状态,上下文等。
function StackScreen() {
return (
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={({ navigation, route }) => ({
headerTitle: (props) => <LogoTitle {...props} />,
})}
/>
</Stack.Navigator>
);
}
function HomeScreen({ navigation }) {
const [count, setCount] = React.useState(0);
// 处理dom的时候用useLayoutEffect 比较好
React.useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => (
<Button onPress={() => setCount((c) => c + 1)} title="Update count" />
),
});
}, [navigation]);
return <Text>Count: {count}</Text>;
}
修改头部返回按钮
可以使用headerBackTitle更改标签行为,并使用headerBackTitleStyle设置它的样式
定制返回按钮
可以通过headerLeft