Skip to main content

给路由传递参数

还记得我说过"稍后在讨论 params 时会详细介绍"吗?现在就是时候了。

现在我们已经知道如何创建一个包含多个路由的堆栈导航器,以及如何在这些路由之间导航,让我们来看看如何在导航到这些路由时传递数据。

包含两个部分:

  1. 通过将参数放在对象中作为 navigation.navigate 函数的第二个参数来传递参数: navigation.navigate('RouteName', { /* 在这里传入参数 */ })
  2. 在你的屏幕组件中读取参数: route.params
note

我们建议你传递的参数是可 JSON 序列化的。这样,你就能够使用状态持久化,并且你的屏幕组件将具有实现深度链接的正确契约。

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 中指定:

{
Details: {
screen: 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 />;
}
note

避免使用 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 或原生应用中的深度链接)将包含用户对象。这是有问题的:

    1. 由于用户对象在 URL 中,可能会传递一个代表不存在的用户或在个人资料中包含错误数据的随机用户对象。
    2. 如果未传递用户对象或格式不正确,这可能会导致崩溃,因为屏幕不知道如何处理它。
    3. URL 可能会变得很长且难以阅读。

更好的方式是在参数中只传递用户的 ID:

navigation.navigate('Profile', { userId: 'jane' });

现在,你可以使用传递的 userId 从全局存储中获取用户。这消除了许多问题,如过时的数据或有问题的 URL。

以下是一些应该包含在参数中的示例:

  1. ID,如用户 id、项目 id 等,例如 navigation.navigate('Profile', { userId: 'Jane' })
  2. 当你有一个项目列表时用于排序、过滤数据等的参数,例如 navigation.navigate('Feeds', { sortBy: 'latest' })
  3. 用于分页的时间戳、页码或游标,例如 navigation.navigate('Chat', { beforeTime: 1603897152675 })
  4. 用于填充屏幕上输入框的数据,例如 navigation.navigate('ComposeTweet', { title: 'Hello world!' })

本质上,在参数中传递识别屏幕所需的最少数据,在很多情况下,这仅仅意味着传递对象的 ID 而不是传递完整的对象。将应用程序数据与导航状态分开。

总结

  • navigatepush 接受一个可选的第二个参数,让你向要导航到的路由传递参数。例如: navigation.navigate('RouteName', { paramName: 'value' })
  • 你可以在屏幕内通过 route.params 读取参数
  • 你可以使用 navigation.setParams 更新屏幕的参数
  • 初始参数可以通过 Screen 上的 initialParams 属性传递
  • 参数应该只包含显示屏幕所需的最少数据,不能再多