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

Bar*_*aes 74 reactjs react-router

我希望我的ReactJS应用程序在导航离开特定页面时通知用户.特别是弹出消息,提醒他/她做一个动作:

"更改已保存,但尚未发布.现在就这样做吗?"

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

我没有在后者身上找到任何东西,我宁愿避开第一个.除非它当然是标准,但这让我想知道如何做这样的事情,而不必将代码添加到用户可以去的每个其他可能的页面..

欢迎任何见解,谢谢!

jca*_*ady 91

react-routerv4引入了一种阻止导航的新方法Prompt.只需将其添加到您要阻止的组件:

import { Prompt } from 'react-router'

const MyComponent = () => (
  <React.Fragment>
    <Prompt
      when={shouldBlockNavigation}
      message='You have unsaved changes, are you sure you want to leave?'
    />
    {/* Component JSX */}
  </React.Fragment>
)
Run Code Online (Sandbox Code Playgroud)

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

componentDidUpdate = () => {
  if (shouldBlockNavigation) {
    window.onbeforeunload = () => true
  } else {
    window.onbeforeunload = undefined
  }
}
Run Code Online (Sandbox Code Playgroud)

onbeforeunload有浏览器的各种支持.

  • 如果你最终使用了 `onbeforeunload`,当你的组件卸载时你会想要清理它。`componentWillUnmount() { window.onbeforeunload = null; }` (11认同)
  • 这导致两个截然不同的警报. (8认同)
  • @XanderStrike您可以尝试设置提示警报的样式,以模仿浏览器的默认警报。不幸的是,没有办法设置onberforeunload警报的样式。 (2认同)

Shu*_*tri 31

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

  1. 附加功能onLeaveRoute
 <Route
      path="/home"
      onEnter={ auth }
      onLeave={ showConfirm }
      component={ Home }
    >
Run Code Online (Sandbox Code Playgroud)
  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?'
    },

    // ...

  })
)
Run Code Online (Sandbox Code Playgroud)

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

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

在某种意义上说

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

react-router v4使用提示或自定义历史记录:

但是,在fromactact-router react-router v4的帮助下,它更容易实现Prompt

根据文件

提示

用于在离开页面之前提示用户.当您的应用程序进入一个应该阻止用户导航的状态时(就像表格填写完整一样),渲染一个<Prompt>.

import { Prompt } from 'react-router'

<Prompt
  when={formIsHalfFilledOut}
  message="Are you sure you want to leave?"
/>
Run Code Online (Sandbox Code Playgroud)

消息:字符串

用户在尝试离开时提示用户的消息.

<Prompt message="Are you sure you want to leave?"/>
Run Code Online (Sandbox Code Playgroud)

消息:func

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

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

什么时候:布尔

<Prompt>您可以随时渲染它而不是有条件地渲染后卫,而是通过when={true}when={false}防止或允许相应的导航.

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

更新:

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

history.js

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

... 
import { history } from 'path/to/history';
<Router history={history}>
  <App/>
</Router>
Run Code Online (Sandbox Code Playgroud)

然后在你的组件中你可以使用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
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 即使您使用“&lt;Prompt&gt;”,当您按提示上的“取消”时,URL 也会发生更改。相关问题:https://github.com/ReactTraining/react-router/issues/5405 (2认同)
  • 在确认路线更改后触发 onLeave。您能否详细说明如何在 onLeave 中取消导航? (2认同)

act*_*eek 23

对于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)
Run Code Online (Sandbox Code Playgroud)

  • RouteLeaveHook回调有什么作用?它是否提示用户使用内置模式?如果你想要一个自定义模式怎么办? (3认同)

小智 8

对于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;
        }
      };
    
    Run Code Online (Sandbox Code Playgroud)

onbeforeunload (文档)

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

    window.onbeforeunload = this.handleOnBeforeUnload;
    
    ...
    
    handleOnBeforeUnload = e => {
      const message = 'Are you sure?';
      e.returnValue = message;
      return message;
    }
    
    Run Code Online (Sandbox Code Playgroud)

以下是我编写的完整组件

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

    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);
    
    Run Code Online (Sandbox Code Playgroud)

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


Eug*_*ski 8

这就是当用户切换到另一个路由或离开当前页面并转到另一个 URL 时显示消息的方式

import PropTypes from 'prop-types'
import React, { useEffect } from 'react'
import { Prompt } from 'react-router-dom'
import { useTranslation } from 'react-i18next'


const LeavePageBlocker = ({ when }) => {
  const { t } = useTranslation()
  const message = t('page_has_unsaved_changes')

  useEffect(() => {
    if (!when) return () => {}

    const beforeUnloadCallback = (event) => {
      event.preventDefault()
      event.returnValue = message
      return message
    }

    window.addEventListener('beforeunload', beforeUnloadCallback)
    return () => {
      window.removeEventListener('beforeunload', beforeUnloadCallback)
    }
  }, [when, message])

  return <Prompt when={when} message={message} />
}

LeavePageBlocker.propTypes = {
  when: PropTypes.bool.isRequired,
}

export default LeavePageBlocker
Run Code Online (Sandbox Code Playgroud)

您的页面:

const [dirty, setDirty] = setState(false)
...
return (
  <>
    <LeavePageBlocker when={dirty} />
    ...
  </>
)
Run Code Online (Sandbox Code Playgroud)

  • 2023 年不再相关,因为 v6 路由器没有提示 (2认同)