Skip to main content
Version: 6.x

防止返回

有时你可能希望阻止用户离开屏幕,例如,如果有未保存的更改,你可能希望显示一个确认对话框。你可以通过使用 beforeRemove 事件来实现它。

¥Sometimes you may want to prevent the user from leaving a screen, for example, if there are unsaved changes, you might want to show a confirmation dialog. You can achieve it by using the beforeRemove event.

事件监听器接收触发它的 action。你可以在确认后再次调度此操作,或检查操作对象以确定要执行的操作。

¥The event listener receives the action that triggered it. You can dispatch this action again after confirmation, or check the action object to determine what to do.

示例:

¥Example:

function EditText({ navigation }) {
const [text, setText] = React.useState('');
const hasUnsavedChanges = Boolean(text);

React.useEffect(
() =>
navigation.addListener('beforeRemove', (e) => {
if (!hasUnsavedChanges) {
// If we don't have unsaved changes, then we don't need to do anything
return;
}

// Prevent default behavior of leaving the screen
e.preventDefault();

// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () => navigation.dispatch(e.data.action),
},
]
);
}),
[navigation, hasUnsavedChanges]
);

return (
<TextInput
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
);
}

以前,执行此操作的方法是:

¥Previously, the way to do this was to:

  • 覆盖标题中的后退按钮

    ¥Override back button in header

  • 禁用向后滑动手势

    ¥Disable back swipe gesture

  • 覆盖 Android 上的系统后退按钮/手势

    ¥Override system back button/gesture on Android

然而,除了代码更少之外,这种方法还有许多重要的区别:

¥However, this approach has many important differences in addition to being less code:

  • 它不与任何特定按钮耦合,从自定义按钮返回也会触发它

    ¥It's not coupled to any specific buttons, going back from custom buttons will trigger it as well

  • 它不与任何特定操作耦合,任何从状态中删除路由的操作都会触发它

    ¥It's not coupled to any specific actions, any action that removes the route from state will trigger it

  • 它适用于嵌套导航器,例如如果由于父导航器中的操作而删除屏幕

    ¥It works across nested navigators, e.g. if the screen is being removed due to an action in parent navigator

  • 用户仍然可以在堆栈导航器中向后滑动,但是,如果事件被阻止,滑动将被取消

    ¥User can still swipe back in the stack navigator, however, the swipe will be cancelled if the event was prevented

  • 可以继续触发事件的相同操作

    ¥It's possible to continue the same action that triggered the event

局限性

¥Limitations

使用 beforeRemove 事件时需要注意一些限制。仅当由于导航状态更改而删除屏幕时才会触发该事件。例如:

¥There are couple of limitations to be aware of when using the beforeRemove event. The event is only triggered whenever a screen is being removed due to a navigation state change. For example:

  • 用户在堆栈中的屏幕上按下后退按钮。

    ¥The user pressed back button on a screen in a stack.

  • 用户执行了向后滑动手势。

    ¥The user performed a swipe back gesture.

  • 调度一些操作(例如 popreset),将屏幕从状态中删除。

    ¥Some action such as pop or reset was dispatched which removes the screen from the state.

当屏幕未聚焦但未移除时,不会触发此事件。例如:

¥This event is not triggered when a screen is being unfocused but not removed. For example:

  • 用户将一个新屏幕推到屏幕顶部,监听器位于堆栈中。

    ¥The user pushed a new screen on top of the screen with the listener in a stack.

  • 用户从一个选项卡/抽屉式屏幕导航到另一选项卡/抽屉式屏幕。

    ¥The user navigated from one tab/drawer screen to another tab/drawer screen.

当用户由于不受导航状态控制的操作而退出屏幕时,也不会触发该事件:

¥The event is also not triggered when the user is exiting the screen due to actions not controlled by the navigation state:

  • 用户关闭应用(例如,通过按主屏幕上的后退按钮、关闭浏览器中的选项卡、从应用切换器中关闭它等)。你还可以在 Android 上使用 hardwareBackPress 事件,在 Web 上使用 beforeunload 事件等来处理其中一些情况。

    ¥The user closes the app (e.g. by pressing the back button on the home screen, closing the tab in the browser, closing it from app switcher etc.). You can additionally use hardwareBackPress event on Android, beforeunload event on Web etc. to handle some of these cases.

  • 由于条件渲染或父组件被卸载,屏幕被卸载。

    ¥A screen gets unmounted due to conditional rendering, or due to a parent component being unmounted.

  • 由于使用 unmountOnBlur 选项和 @react-navigation/bottom-tabs@react-navigation/drawer 等,屏幕被卸载。

    ¥A screen gets unmounted due to usage of unmountOnBlur options with @react-navigation/bottom-tabs, @react-navigation/drawer etc.

除了上述情况外,该功能在 @react-navigation/native-stack 上也无法正常工作。为了使这项工作有效,你需要:

¥In addition to the above scenarios, this feature also doesn't work properly with @react-navigation/native-stack. To make this work, you need to:

  • 禁用屏幕滑动手势 (gestureEnabled: false)。

    ¥Disable the swipe gesture for the screen (gestureEnabled: false).

  • 使用自定义后退按钮 (headerLeft: (props) => <CustomBackButton {...props} />) 覆盖标头中的原生后退按钮。

    ¥Override the native back button in the header with a custom back button (headerLeft: (props) => <CustomBackButton {...props} />).