Skip to main content

屏幕间的跳转

在上一节中,我们定义了一个包含两个路由(HomeDetails)的堆栈导航器,但我们还没有学习如何让用户从 Home 导航到 Details(虽然我们确实学会了如何在代码中更改_初始_路由,但是强制用户克隆我们的代码库并修改代码才能看到另一个屏幕,这可能是最糟糕的用户体验之一)。

如果这是在网页浏览器中,我们可以这样写:

<a href="details.html">Go to Details</a>

另一种写法是:

<a
onClick={() => {
window.location.href = 'details.html';
}}
>
Go to Details
</a>

我们将采用类似于后者的方式,但不使用全局的 window.location,而是使用在屏幕组件中可访问的 navigation 对象。

导航到新屏幕

// codeblock-focus-start
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';

function HomeScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
</View>
);
}

// ... other code from the previous section
// codeblock-focus-end

function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}

让我们来分析一下:

  • navigation - navigation 对象由 useNavigation hook 返回(稍后在"深入理解 navigation 对象"中会详细介绍)。
  • navigate('Details') - 我们调用 navigate 函数(在 navigation 对象上 — 命名确实很难!),并传入我们想要让用户跳转到的路由名称。
note

如果我们调用 navigation.navigate 时传入了一个未在导航器中定义的路由名称,在开发版本中会打印错误,而在生产版本中则不会有任何反应。换句话说,我们只能导航到已在导航器中定义的路由 — 我们不能导航到任意组件。

现在我们有了一个包含两个路由的堆栈:1) Home 路由 2) Details 路由。如果我们在 Details 屏幕中再次导航到 Details 路由会发生什么呢?

多次导航到同一屏幕

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';

function HomeScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
</View>
);
}

// codeblock-focus-start
function DetailsScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details... again
</Button>
</View>
);
}
// codeblock-focus-end

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}

如果你运行这段代码,你会发现当点击"Go to Details... again"时,什么都没有发生!这是因为我们已经在 Details 路由上了。navigate 函数大致意味着"转到这个屏幕",如果你已经在那个屏幕上,那么它不做任何操作是合理的。

假设我们实际上_想要_添加另一个详情屏幕。这在需要为每个路由传递一些唯一数据的情况下很常见(稍后我们讨论 params 时会详细介绍!)。为此,我们可以将 navigate 改为 push。这样我们就可以表达添加另一个路由的意图,而不用考虑现有的导航历史。

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';

function HomeScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
</View>
);
}

function DetailsScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
// codeblock-focus-start
<Button onPress={() => navigation.push('Details')}>
Go to Details... again
</Button>
// codeblock-focus-end
</View>
);
}

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}

每次调用 push 时,我们都会向导航堆栈添加一个新路由。而当你调用 navigate 时,只有在你还没有在该路由上时才会推入新路由。

返回

当可以从当前屏幕返回时(如果导航堆栈中只有一个屏幕,就没有可以返回的地方,因此也就没有返回按钮),原生堆栈导航器提供的标题栏会自动包含一个返回按钮。

有时你可能想要以编程方式触发这个行为,这时你可以使用 navigation.goBack()

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';

function HomeScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
</View>
);
}

// codeblock-focus-start
function DetailsScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button onPress={() => navigation.push('Details')}>
Go to Details... again
</Button>
<Button onPress={() => navigation.goBack()}>Go back</Button>
</View>
);
}
// codeblock-focus-end

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}
note

在 Android 上,React Navigation 会监听硬件返回按钮,当用户按下它时会自动为你调用 goBack() 函数,因此它的行为符合用户的预期。

另一个常见需求是能够返回_多个_屏幕 -- 例如,如果你在堆栈中深入了几个屏幕,想要全部关闭并返回到第一个屏幕。在这种情况下,我们知道要返回到 Home,所以可以使用 popTo('Home')。另一种选择是 navigation.popToTop(),它会返回到堆栈中的第一个屏幕。

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';

function HomeScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')}>
Go to Details
</Button>
</View>
);
}

// codeblock-focus-start
function DetailsScreen() {
const navigation = useNavigation();

return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
<Button onPress={() => navigation.push('Details')}>
Go to Details... again
</Button>
<Button onPress={() => navigation.goBack()}>Go back</Button>
<Button onPress={() => navigation.popTo('Home')}>Go to Home</Button>
<Button onPress={() => navigation.popToTop()}>
Go back to first screen in stack
</Button>
</View>
);
}
// codeblock-focus-end

const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screens: {
Home: HomeScreen,
Details: DetailsScreen,
},
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
return <Navigation />;
}

总结

  • navigation.navigate('RouteName') 如果你还没有在该路由上,它会向原生堆栈导航器推入一个新路由。
  • 我们可以根据需要多次调用 navigation.push('RouteName'),它会继续推入路由。
  • 标题栏会自动显示返回按钮,但你也可以通过调用 navigation.goBack() 以编程方式返回。在 Android 上,硬件返回按钮会按预期工作。
  • 你可以使用 navigation.popTo('RouteName') 返回到堆栈中的某个已存在的屏幕,也可以使用 navigation.popToTop() 返回到堆栈中的第一个屏幕。
  • 通过 useNavigation hook,所有屏幕组件都可以访问 navigation 对象。