给路由传递参数
还记得我说过"稍后在讨论 params 时会详细介绍"吗?现在就是时候了。
现在我们已经知道如何创建一个包含多个路由的堆栈导航器,以及如何在这些路由之间导航,让我们来看看如何在导航到这些路由时传递数据。
包含两个部分:
- 通过将参数放在对象中作为
navigation.navigate函数的第二个参数来传递参数:navigation.navigate('RouteName', { /* 在这里传入参数 */ }) - 在你的屏幕组件中读取参数:
route.params
import * as React from 'react';
import { View, Text } from 'react-native';
import {
createStaticNavigation,
useNavigation,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Button } from '@react-navigation/elements';
// codeblock-focus-start
function HomeScreen() {
const navigation = useNavigation();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
onPress={() => {
/* 1. 导航至 Details 路由,并携带参数 */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
>
Go to Details
</Button>
</View>
);
}
function DetailsScreen({ route }) {
const navigation = useNavigation();
/* 2. 获取参数 */
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
onPress={
() =>
navigation.push('Details', {
itemId: Math.floor(Math.random() * 100),
})
}
>
Go to Details... again
</Button>
<Button onPress={() => navigation.navigate('Home')}>Go to Home</Button>
<Button onPress={() => navigation.goBack()}>Go back</Button>
</View>
);
}
// codeblock-focus-end
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
初始参数
你也可以为屏幕传递一些初始参数。如果你在导航到此屏幕时没有指定任何参数,将使用 这些初始参数。它们也会与你传递的任何参数进行浅合并。初始参数可以在 initialParams 中指定:
- Static
- Dynamic
{
Details: {
screen: DetailsScreen,
initialParams: { itemId: 42 },
},
}
<Stack.Screen
name="Details"
component={DetailsScreen}
initialParams={{ itemId: 42 }}
/>
更新参数
屏幕也可以更新它们的参数,就像它们可以更新状态一样。navigation.setParams 方法允许你更新屏幕的参数。有关更多详细信息,请参阅 setParams 的 API 参考。
基本用法:
import * as React from 'react';
import { Text, View } from 'react-native';
import {
createStaticNavigation,
useNavigation,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Button } from '@react-navigation/elements';
function HomeScreen({ route }) {
const navigation = useNavigation();
const { itemId } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Button
onPress={
() =>
// codeblock-focus-start
navigation.setParams({
itemId: Math.floor(Math.random() * 100),
})
// codeblock-focus-end
}
>
Update param
</Button>
</View>
);
}
const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
initialParams: { itemId: 42 },
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
避免使用 setParams 来更新屏幕选项,如 title 等。如果你需要更新选项,请使用 setOptions。
向上一个屏幕传递参数
参数不仅可以用于向新屏幕传递数据,还可以用于 向上一个屏幕传递数据。例如,假设你有一个带有"创建帖子"按钮的屏幕,该按钮打开一个新屏幕来创建帖子。创建帖子后,你想将帖子数据传回上一个屏幕。
要实现这一点,你可以使用 popTo 方法返回到上一个屏幕,同时向其传递参数:
import * as React from 'react';
import { Text, View, TextInput } from 'react-native';
import {
createStaticNavigation,
useNavigation,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Button } from '@react-navigation/elements';
// codeblock-focus-start
function HomeScreen({ route }) {
const navigation = useNavigation();
// Use an effect to monitor the update to params
React.useEffect(() => {
if (route.params?.post) {
// Post updated, do something with `route.params.post`
// For example, send the post to the server
alert('New post: ' + route.params?.post);
}
}, [route.params?.post]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Button onPress={() => navigation.navigate('CreatePost')}>
Create post
</Button>
<Text style={{ margin: 10 }}>Post: {route.params?.post}</Text>
</View>
);
}
function CreatePostScreen({ route }) {
const navigation = useNavigation();
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
onPress={() => {
// Pass params back to home screen
navigation.popTo('Home', { post: postText });
}}
>
Done
</Button>
</>
);
}
// codeblock-focus-end
const RootStack = createNativeStackNavigator({
screens: {
Home: HomeScreen,
CreatePost: CreatePostScreen,
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
此时,按下“完成”后,主屏幕的route.params将更新为你在navigate中传入的文本。
向嵌套屏幕传递参数
如果你有嵌套的导航器,你需要以稍微不同的方式传递参数。例如,假设你在 More 屏幕中有一个导航器,并且想要向该导航器中的 Settings 屏幕传递参数。那么你可以按以下方式传递参数:
import * as React from 'react';
import { Text, View, TextInput } from 'react-native';
import {
createStaticNavigation,
useNavigation,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Button } from '@react-navigation/elements';
function SettingsScreen({ route }) {
const navigation = useNavigation();
const { user } = route.params;
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Settings Screen</Text>
<Text>userParam: {JSON.stringify(user)}</Text>
<Button onPress={() => navigation.navigate('Profile')}>
Go to Profile
</Button>
</View>
);
}
function ProfileScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile Screen</Text>
</View>
);
}
function HomeScreen() {
const navigation = useNavigation();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
onPress={
() =>
// codeblock-focus-start
navigation.navigate('More', {
screen: 'Settings',
params: { user: 'jane' },
})
// codeblock-focus-end
}
>
Go to Settings
</Button>
</View>
);
}
const MoreStack = createNativeStackNavigator({
screens: {
Settings: SettingsScreen,
Profile: ProfileScreen,
},
});
const RootTabs = createBottomTabNavigator({
screens: {
Home: HomeScreen,
More: MoreStack,
},
});
const Navigation = createStaticNavigation(RootTabs);
export default function App() {
return <Navigation />;
}
参见嵌套导航器了解更多关于嵌套的详细信息。
参数中应该包含什么
参数本质上是屏幕的选项。它们应该只包含显示屏幕所需的最少数据,不能再多。如果数据被多个屏幕使用,它应该存在于全局存储或全局缓存中。参数不是为状态管理设计的。
你可以将路由对象视为 URL。如果你的屏幕有一个 URL,URL 中应该包含什么?同样的原则也适用于参数。想想访问购物网站时;当你看到产品列表时,URL 通常包含类别名称、排序类型、任何过滤器等,而不是屏幕上显示的实际产品列表。
例如,假设你有一个 Profile 屏幕。在导航到该屏幕时,你可能会想要在参数中传递用户对象:
// Don't do this
navigation.navigate('Profile', {
user: {
id: 'jane',
firstName: 'Jane',
lastName: 'Done',
age: 25,
},
});
这看起来很方便,让你可以通过 route.params.user 访问用户对象,而无需任何额外的工作。
然而,这是一种反模式。这样做有很多原因是不好的:
-
相同的数据在多个地方重复。这可能导致错误,例如即使用户对象在导航后发生了变化,个人资料屏幕仍显示过时的数据。
-
现在每个导航到
Profile屏幕的屏幕都需要知道如何获取用户对象 - 这增加了代码的复杂性。 -
指向该屏幕的 URL(网页上的浏览器 URL 或原生应用中的深度链接)将包含用户对象。这是有问题的:
- 由于用户对象在 URL 中,可能会传递一个代表不存在的用户或在个人 资料中包含错误数据的随机用户对象。
- 如果未传递用户对象或格式不正确,这可能会导致崩溃,因为屏幕不知道如何处理它。
- URL 可能会变得很长且难以阅读。
更好的方式是在参数中只传递用户的 ID:
navigation.navigate('Profile', { userId: 'jane' });
现在,你可以使用传递的 userId 从全局存储中获取用户。这消除了许多问题,如过时的数据或有问题的 URL。
以下是一些应该包含在参数中的示例:
- ID,如用户 id、项目 id 等,例如
navigation.navigate('Profile', { userId: 'Jane' }) - 当你有一个项目列表时用于排序、过滤数据等的参数,例如
navigation.navigate('Feeds', { sortBy: 'latest' }) - 用于分页的时间戳、页码或游标,例如
navigation.navigate('Chat', { beforeTime: 1603897152675 }) - 用于填充屏幕上输入框的数据,例如
navigation.navigate('ComposeTweet', { title: 'Hello world!' })
本质上,在参数中传递识别屏幕所需的最少数据,在很多情况下,这仅仅意味着传递对象的 ID 而不是传递完整的对象。将应用程序数据与导航状态分开。
总结
navigate和push接受一个可选的第二个参数,让你向要导航到的路由传递参数。例如:navigation.navigate('RouteName', { paramName: 'value' })。- 你可以在屏幕内通过
route.params读取参数 - 你可以使用
navigation.setParams更新屏幕的参数 - 初始参数可以通过
Screen上的initialParams属性传递 - 参数应该只包含显示屏幕所需的最少数据,不能再多