使用 react-router 检测用户离开页面

IT技术 reactjs react-router
2021-04-13 00:07:19

我希望我的 ReactJS 应用程序在离开特定页面时通知用户。特别是提醒他/她执行操作的弹出消息:

“更改已保存,但尚未发布。现在执行吗?”

我应该react-router全局触发它,还是可以从react页面/组件内完成?

我没有发现关于后者的任何内容,我宁愿避免第一个。当然,除非它是常态,但这让我想知道如何做这样的事情而不必向用户可以访问的每个其他可能的页面添加代码..

欢迎任何见解,谢谢!

6个回答

react-routerv4 引入了一种使用Prompt. 只需将此添加到您要阻止的组件中:

import { Prompt } from 'react-router'

const MyComponent = () => (
  <>
    <Prompt
      when={shouldBlockNavigation}
      message='You have unsaved changes, are you sure you want to leave?'
    />
    {/* Component JSX */}
  </>
)

这将阻止任何路由,但不会阻止页面刷新或关闭。要阻止它,您需要添加以下内容(根据需要使用适当的 React 生命周期进行更新):

componentDidUpdate = () => {
  if (shouldBlockNavigation) {
    window.onbeforeunload = () => true
  } else {
    window.onbeforeunload = undefined
  }
}

onbeforeunload有多种浏览器支持。

如果您最终使用了onbeforeunload ,您将需要在卸载组件时将其清理干净。 componentWillUnmount() { window.onbeforeunload = null; }
2021-05-27 00:07:19
@ReneEnriquezreact-router不支持开箱即用(假设取消按钮不会触发任何路线更改)。不过,您可以创建自己的模态来模仿行为。
2021-06-01 00:07:19
片段可以缩短为 <></>
2021-06-04 00:07:19
@XanderStrike 您可以尝试设置提示警报的样式以模仿浏览器默认警报。不幸的是,没有办法设置onberforeunload警报的样式
2021-06-09 00:07:19
不过,这会导致两个外观截然不同的警报。
2021-06-12 00:07:19

在 react-routerv2.4.0或更高版本和之前v4有几个选项

  1. 附加功能onLeaveRoute
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
    
  1. 使用功能setRouteLeaveHookcomponentDidMount

您可以防止发生转换或在离开带有离开钩子的路线之前提示用户。

const Home = withRouter(
  React.createClass({

    componentDidMount() {
      this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave)
    },

    routerWillLeave(nextLocation) {
      // return false to prevent a transition w/o prompting the user,
      // or return a string to allow the user to decide:
      // return `null` or nothing to let other hooks to be executed
      //
      // NOTE: if you return true, other hooks will not be executed!
      if (!this.state.isSaved)
        return 'Your work is not saved! Are you sure you want to leave?'
    },

    // ...

  })
)

请注意,此示例使用了withRouter引入的高阶组件v2.4.0.

但是,当手动更改 URL 中的路由时,这些解决方案并不完美

在某种意义上说

  • 我们看到确认 - 好的
  • 包含页面不会重新加载 - 好的
  • URL 不会改变 - 不好

对于react-router v4使用提示或自定义的历史:

然而,在from'react-routerreact-router v4的帮助下,它更容易实现Prompt

根据文档

迅速的

用于在离开页面之前提示用户。当您的应用程序进入应阻止用户导航离开的状态时(如表单填写一半),渲染<Prompt>.

import { Prompt } from 'react-router'

<Prompt
  when={formIsHalfFilledOut}
  message="Are you sure you want to leave?"
/>

消息:字符串

当用户尝试离开时提示用户的消息。

<Prompt message="Are you sure you want to leave?"/>

消息:功能

将使用用户尝试导航到的下一个位置和操作来调用。返回一个字符串以向用户显示提示或返回 true 以允许转换。

<Prompt message={location => (
  `Are you sure you want to go to ${location.pathname}?`
)}/>

什么时候:布尔

不是有条件地<Prompt>在守卫后面渲染 a ,您可以始终渲染它但通过when={true}或相应when={false}地阻止或允许导航。

在您的渲染方法中,您只需根据需要添加文档中提到的内容。

更新:

如果您希望在用户离开页面时执行自定义操作,您可以使用自定义历史记录并配置您的路由器,例如

历史.js

import createBrowserHistory from 'history/createBrowserHistory'
export const history = createBrowserHistory()

... 
import { history } from 'path/to/history';
<Router history={history}>
  <App/>
</Router>

然后在您的组件中,您可以使用history.blocklike

import { history } from 'path/to/history';
class MyComponent extends React.Component {
   componentDidMount() {
      this.unblock = history.block(targetLocation => {
           // take your action here     
           return false;
      });
   }
   componentWillUnmount() {
      this.unblock();
   }
   render() {
      //component render here
   }
}
在确认路线更改后触发 onLeave。您能否详细说明如何在 onLeave 中取消导航?
2021-05-23 00:07:19
history.block() 是运行任何“路由前”逻辑的好方法,例如存储滚动位置。
2021-05-25 00:07:19
即使您使用<Prompt>URL,当您在提示中按取消时也会更改。相关问题:github.com/ReactTraining/react-router/issues/5405
2021-06-06 00:07:19
我看到这个问题仍然很受欢迎。由于最佳答案是针对旧的已弃用版本,因此我将这个较新的答案设置为已接受的答案。现在可以在github.com/ReactTraining/react-router 上找到旧文档(和升级指南)的链接
2021-06-06 00:07:19

对于react-router2.4.0+

注意:建议将所有代码迁移到最新版本react-router以获取所有新内容。

正如react-router 文档中所推荐的

应该使用withRouter高阶组件:

我们认为这个新的 HoC 更好、更容易,并将在文档和示例中使用它,但切换并不是硬性要求。

作为文档中的 ES6 示例:

import React from 'react'
import { withRouter } from 'react-router'

const Page = React.createClass({

  componentDidMount() {
    this.props.router.setRouteLeaveHook(this.props.route, () => {
      if (this.state.unsaved)
        return 'You have unsaved information, are you sure you want to leave this page?'
    })
  }

  render() {
    return <div>Stuff</div>
  }

})

export default withRouter(Page)
RouteLeaveHook 回调有什么作用?它是否使用内置模式提示用户?如果您想要自定义模态怎么办
2021-05-24 00:07:19
像@Learner:如何使用自定义确认框(如 vex 或 SweetAlert 或其他非阻塞对话框)来执行此操作?
2021-05-25 00:07:19
@MustafaMamun 这似乎是一个不相关的问题,您主要想创建一个新问题来解释细节。
2021-06-02 00:07:19
我在尝试使用解决方案时遇到了问题。该组件必须直接连接到路由器,然后此解决方案才有效。我在那里找到了更好的答案stackoverflow.com/questions/39103684/...
2021-06-10 00:07:19
我遇到以下错误:- TypeError:无法读取未定义的属性“ id ”。任何建议
2021-06-14 00:07:19

对于react-routerv3.x

我遇到了同样的问题,我需要对页面上任何未保存更改的确认消息。在我的例子中,我使用的是React Router v3,所以我不能使用<Prompt />,它是从React Router v4引入的

我处理“后退按钮点击”和“意外的链接点击”用的组合setRouteLeaveHookhistory.pushState(),并办理“刷新按钮”与onbeforeunload事件处理程序。

setRouteLeaveHook ( doc ) & history.pushState ( doc )

  • 仅使用 setRouteLeaveHook 是不够的。出于某种原因,尽管单击“后退按钮”时页面保持不变,但 URL 已更改。

      // setRouteLeaveHook returns the unregister method
      this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
        this.props.route,
        this.routerWillLeave
      );
    
      ...
    
      routerWillLeave = nextLocation => {
        // Using native 'confirm' method to show confirmation message
        const result = confirm('Unsaved work will be lost');
        if (result) {
          // navigation confirmed
          return true;
        } else {
          // navigation canceled, pushing the previous path
          window.history.pushState(null, null, this.props.route.path);
          return false;
        }
      };
    

onbeforeunload (文档)

  • 它用于处理“意外重新加载”按钮

    window.onbeforeunload = this.handleOnBeforeUnload;
    
    ...
    
    handleOnBeforeUnload = e => {
      const message = 'Are you sure?';
      e.returnValue = message;
      return message;
    }
    

以下是我编写的完整组件

  • 请注意,withRouter用于拥有this.props.router.
  • 注意this.props.route是从调用组件传递下来的
  • 请注意,currentState作为props传递以具有初始状态并检查任何更改

    import React from 'react';
    import PropTypes from 'prop-types';
    import _ from 'lodash';
    import { withRouter } from 'react-router';
    import Component from '../Component';
    import styles from './PreventRouteChange.css';
    
    class PreventRouteChange extends Component {
      constructor(props) {
        super(props);
        this.state = {
          // initialize the initial state to check any change
          initialState: _.cloneDeep(props.currentState),
          hookMounted: false
        };
      }
    
      componentDidUpdate() {
    
       // I used the library called 'lodash'
       // but you can use your own way to check any unsaved changed
        const unsaved = !_.isEqual(
          this.state.initialState,
          this.props.currentState
        );
    
        if (!unsaved && this.state.hookMounted) {
          // unregister hooks
          this.setState({ hookMounted: false });
          this.unregisterRouteHook();
          window.onbeforeunload = null;
        } else if (unsaved && !this.state.hookMounted) {
          // register hooks
          this.setState({ hookMounted: true });
          this.unregisterRouteHook = this.props.router.setRouteLeaveHook(
            this.props.route,
            this.routerWillLeave
          );
          window.onbeforeunload = this.handleOnBeforeUnload;
        }
      }
    
      componentWillUnmount() {
        // unregister onbeforeunload event handler
        window.onbeforeunload = null;
      }
    
      handleOnBeforeUnload = e => {
        const message = 'Are you sure?';
        e.returnValue = message;
        return message;
      };
    
      routerWillLeave = nextLocation => {
        const result = confirm('Unsaved work will be lost');
        if (result) {
          return true;
        } else {
          window.history.pushState(null, null, this.props.route.path);
          if (this.formStartEle) {
            this.moveTo.move(this.formStartEle);
          }
          return false;
        }
      };
    
      render() {
        return (
          <div>
            {this.props.children}
          </div>
        );
      }
    }
    
    PreventRouteChange.propTypes = propTypes;
    
    export default withRouter(PreventRouteChange);
    

如果有任何问题,请告诉我:)

谢谢!window.history.pushState() 的技巧真的很有帮助
2021-06-15 00:07:19
this.formStartEle 究竟来自哪里?
2021-06-19 00:07:19

使用 history.listen

例如像下面这样:

在您的组件中,

componentWillMount() {
    this.props.history.listen(() => {
      // Detecting, user has changed URL
      console.info(this.props.history.location.pathname);
    });
}
您好,欢迎来到 Stack Overflow。在回答一个已经有很多答案的问题时,请务必添加一些额外的见解,说明为什么您提供的回复是实质性的,而不是简单地回应原始发布者已经审查过的内容。这在“纯代码”答案中尤其重要,例如您提供的答案。
2021-06-12 00:07:19