在 React-router 中拦截/处理浏览器的后退按钮?

Posted

技术标签:

【中文标题】在 React-router 中拦截/处理浏览器的后退按钮?【英文标题】:Intercept/handle browser's back button in React-router? 【发布时间】:2017-01-13 12:13:22 【问题描述】:

我正在使用 Material-ui 的选项卡,这些选项卡是受控的,我将它们用于(React-router)这样的链接:

    <Tab value=0 label="dashboard" containerElement=<Link to="/dashboard/home"/>/>
    <Tab value=1 label="users" containerElement=<Link to="/dashboard/users"/> />
  <Tab value=2 label="data" containerElement=<Link to="/dashboard/data"/> />

如果我正在访问仪表板/数据并单击浏览器的后退按钮 我去(例如)仪表板/用户,但突出显示的选项卡仍然停留在仪表板/数据上(值=2)

我可以通过设置状态来改变,但是不知道当浏览器的后退按钮被按下时如何处理?

我发现了这个:

window.onpopstate = this.onBackButtonEvent;

但每次状态更改时都会调用此方法(不仅在后退按钮事件中)

【问题讨论】:

【参考方案1】:

这是一个有点老的问题,你可能已经得到了答案,但是对于像我这样需要这个的人,我将留下这个答案。

使用 react-router 使工作变得简单:

import  browserHistory  from 'react-router';

componentDidMount() 
    super.componentDidMount();

    this.onScrollNearBottom(this.scrollToLoad);

    this.backListener = browserHistory.listen(location => 
      if (location.action === "POP") 
        // Do your stuff
      
    );
  

componentWillUnmount() 
    super.componentWillUnmount();
    // Unbind listener
    this.backListener();

【讨论】:

对我不起作用。使用 react-router@3.0.5,location.action 总是 PUSH,即使点击浏览器后退按钮 @loopmode 当您处理后退按钮操作时,您可以使用 browserHistory.goBack()?? 我发现在其他地方,一位同事正在手动干预历史记录,类似于 if (nextLocation.action === 'POP' && getStepIndex(nextLocation.pathname) === 0) browserHistory.push(路径名:$getPathForIndex(0));返回假; 所以..if POP then PUSH 为了使前进按钮不可用(在没有提交表单的情况下继续前进)所以..你的答案仍然正确 - 我这边的用户错误:) 从 react-router@5.0.0 开始,您无法导入 browserHistory,如本回复所示。似乎history 包含在传递给从路由引用的任何组件的props 中。如果不完全正确,请随时纠正我。 对于使用 React Router 4+ 的任何人,监听有 2 个参数,位置和动作。 history.listen((loc, action) =&gt; if (action === 'POP') // do stuff)【参考方案2】:

使用钩子可以检测后退和前进按钮

import  useHistory  from 'react-router-dom'


const [ locationKeys, setLocationKeys ] = useState([])
const history = useHistory()

useEffect(() => 
  return history.listen(location => 
    if (history.action === 'PUSH') 
      setLocationKeys([ location.key ])
    

    if (history.action === 'POP') 
      if (locationKeys[1] === location.key) 
        setLocationKeys(([ _, ...keys ]) => keys)

        // Handle forward event

       else 
        setLocationKeys((keys) => [ location.key, ...keys ])

        // Handle back event

      
    
  )
, [ locationKeys, ])

【讨论】:

setLocationKeys(([ _, ...keys ]) => keys)中的_(下划线)是什么 @ShabbirEssaji 它使用destructing 和扩展运算符返回一个删除了第一个元素的数组。 This may help @ShabbirEssaji 您现在可能已经找到了答案,但是当必须分配变量但不会使用时,通常会使用下划线。 这仍然是处理前后处理的好方法,还是添加了任何东西来处理可以说是 hacky locationKeys, setLocationKeys 位?然而。请。 ty 到目前为止,是的,这是我们拥有的最佳解决方案。【参考方案3】:

这是我最终的做法:

componentDidMount() 
    this._isMounted = true;
    window.onpopstate = ()=> 
      if(this._isMounted) 
        const  hash  = location;
        if(hash.indexOf('home')>-1 && this.state.value!==0)
          this.setState(value: 0)
        if(hash.indexOf('users')>-1 && this.state.value!==1)
          this.setState(value: 1)
        if(hash.indexOf('data')>-1 && this.state.value!==2)
          this.setState(value: 2)
      
    
  

感谢大家的帮助,哈哈

【讨论】:

这不是反应方式 “反应方式”过于严格和拜占庭式。【参考方案4】:

钩子示例

const history = useRouter();
  useEffect(() => 
    return () => 
      // && history.location.pathname === "any specific path")
      if (history.action === "POP") 
        history.replace(history.location.pathname, /* the new state */);
      
    ;
  , [history])

我不使用history.listen,因为它不影响状态

const disposeListener = history.listen(navData => 
        if (navData.pathname === "/props") 
            navData.state = /* the new state */;
        
    );

【讨论】:

我会将依赖数组更改为 [history.location, history.action] 因为它不会捕获位置更改 useRouter() 不是特定于 Next.js 框架的吗? 我只能在useHooks 库中找到useRouter() 函数:usehooks.com/useRouter【参考方案5】:

这个问题的大多数答案要么使用过时的 React Router 版本,要么依赖于不太现代的类组件,要么令人困惑;并且没有人使用 Typescript,这是一种常见的组合。这是使用 Router v5、函数组件和 Typescript 的答案:

// use destructuring to access the history property of the ReactComponentProps type
function MyComponent(  history : ReactComponentProps) 

    // use useEffect to access lifecycle methods, as componentDidMount etc. are not available on function components.
    useEffect(() => 

        return () => 
            if (history.action === "POP") 
                // Code here will run when back button fires. Note that it's after the `return` for useEffect's callback; code before the return will fire after the page mounts, code after when it is about to unmount.
                
           
    )

可以在here找到更完整的示例和解释。

【讨论】:

【参考方案6】:

React Router API 3.x 版有a set of utilities,您可以使用它在事件注册到浏览器历史记录之前公开“返回”按钮事件。您必须首先将您的组件包装在withRouter() higher-order component 中。然后您可以使用setRouteLeaveHook() 函数,该函数接受任何具有有效path 属性和回调函数的route 对象。

import Component from 'react';
import withRouter from 'react-router';

class Foo extends Component 
  componentDidMount() 
    this.props.router.setRouteLeaveHook(this.props.route, this.routerWillLeave);
  

  routerWillLeave(nextState)  // return false to block navigation, true to allow
    if (nextState.action === 'POP') 
      // handle "Back" button clicks here
    
  


export default withRouter(Foo);

【讨论】:

对我来说它只是给出一个错误TypeError: Cannot read property 'setRouteLeaveHook' of undefined @NikitaVlasenko 扩展上面的例子,Foo 需要传递给&lt;Route /&gt; 组件,或者至少需要继承路由组件的 props。 (例如,在您的 routes.js 文件中,&lt;Route component=Foo&gt;/* ... */&lt;/Route&gt;【参考方案7】:

我使用 withrouter hoc 来获取历史道具并编写一个 componentDidMount() 方法:

componentDidMount() 
    if (this.props.history.action === "POP") 
        // custom back button implementation
    

【讨论】:

点击后退按钮前触发,您能帮忙吗?,我们需要在点击后退按钮后触发自定义弹出窗口。【参考方案8】:

用于在响应功能组件中按下浏览器时发出警告。执行以下步骤

    声明 isBackButtonClicked 并将其初始化为 false 并使用 setBackbuttonPress 函数保持状态。
const [isBackButtonClicked, setBackbuttonPress] = useState(false);
    在componentdidmount中,添加以下代码行
window.history.pushState(null, null, window.location.pathname);
window.addEventListener('popstate', onBackButtonEvent);

    定义 onBackButtonEvent 函数并根据您的要求编写逻辑。

      const onBackButtonEvent = (e) => 
      e.preventDefault();
      if (!isBackButtonClicked) 
    
      if (window.confirm("Do you want to go to Test Listing")) 
        setBackbuttonPress(true)
        props.history.go(listingpage)
       else 
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      
    
    

    在组件中将挂载退订onBackButtonEvent函数

最终代码如下所示

import React,useEffect,useState from 'react'

function HandleBrowserBackButton() 
  const [isBackButtonClicked, setBackbuttonPress] = useState(false)

  useEffect(() => 

    window.history.pushState(null, null, window.location.pathname);
    window.addEventListener('popstate', onBackButtonEvent);

    //logic for showing popup warning on page refresh
    window.onbeforeunload = function () 

      return "Data will be lost if you leave the page, are you sure?";
    ;
    return () => 
      window.removeEventListener('popstate', onBackButtonEvent);
    

    // eslint-disable-next-line react-hooks/exhaustive-deps
  , []);
  const onBackButtonEvent = (e) => 
    e.preventDefault();
    if (!isBackButtonClicked) 

      if (window.confirm("Do you want to go to Test Listing")) 
        setBackbuttonPress(true)
        props.history.go(listingpage)
       else 
        window.history.pushState(null, null, window.location.pathname);
        setBackbuttonPress(false)
      
    
  

  return (
    <div>

    </div>
  )


export default HandleBrowserBackButton

【讨论】:

【参考方案9】:

使用钩子。我已将@Nicolas Keller 的代码转换为打字稿

  const [locationKeys, setLocationKeys] = useState<(string | undefined)[]>([]);
  const history = useHistory();

  useEffect(() => 
    return history.listen((location) => 
      if (history.action === 'PUSH') 
        if (location.key) setLocationKeys([location.key]);
      

      if (history.action === 'POP') 
        if (locationKeys[1] === location.key) 
          setLocationKeys(([_, ...keys]) => keys);

          // Handle forward event
          console.log('forward button');
         else 
          setLocationKeys((keys) => [location.key, ...keys]);

          // Handle back event
          console.log('back button');
          removeTask();
        
      
    );
  , [locationKeys]);

【讨论】:

_是做什么的【参考方案10】:

如果你使用的是 React Router V5,你可以试试Prompt。

用于在离开页面之前提示用户。当您的应用程序进入应该阻止用户导航离开的状态时(例如表单填写了一半),呈现一个

<Prompt
   message=(location, action) => 
   if (action === 'POP') 
      console.log("Backing up...")
      // Add your back logic here
   

   return true;
   
/>

【讨论】:

你应该包括后面的逻辑【参考方案11】:

这取决于你在 React 中使用的路由器类型。

如果您使用 react-router 中的 BrowserRouter(但在 react-router v4 中不可用),如上所述,您可以使用操作 'POP' 拦截浏览器后退按钮。

但是,如果您使用 HashRouter 推送路由,上述解决方案将不起作用。原因是当您单击浏览器后退按钮或从组件推送路由时,哈希路由器总是通过“POP”操作触发。您无法简单地使用 window.popstate 或 history.listen 区分这两个操作。

【讨论】:

【参考方案12】:

只需放入 componentDidMount()

componentDidMount() 
    window.onbeforeunload =this.beforeUnloadListener;
 
beforeUnloadListener = (event) => 
    event.preventDefault();
    return event.returnValue = "Are you sure you want to exit?";
;

【讨论】:

【参考方案13】:

在 NextJs 中,我们可以使用 beforePopState 函数并做我们想要的关闭模态或显示模态或检查返回地址并决定做什么

const router = useRouter();

useEffect(() => 
    router.beforePopState(( url, as, options ) => 
        // I only want to allow these two routes!

        if (as === '/' ) 
            // Have s-s-r render bad routes as a 404.
             window.location.href = as;
            closeModal();
            return false
        

        return true
    )
, [])

【讨论】:

【参考方案14】:

即将发布的 6.0 版引入了 useBlocker 挂钩 - 可用于拦截所有导航尝试。

import  Action  from 'history';
import  useBlocker  from 'react-router';

// when blocker should be active
const unsavedChanges = true;

useBlocker((transition) => 
    const 
        location, // The new location
        action, // The action that triggered the change
     = transition;

    // intercept back and forward actions:
    if (action === Action.Pop) 
        alert('intercepted!')
    

, unsavedChanges);

【讨论】:

【参考方案15】:

将这两行添加到您的 componentDidMount() 中。这对我有用

window.history.pushState(null, null, document.URL);
window.addEventListener('popstate', function(event) 
      window.location.replace(
        `YOUR URL`
      );
);

【讨论】:

【参考方案16】:

您可以使用“withrouter” HOC 并使用this.props.history.goBack

<Button onClick=this.props.history.goBack>
    BACK
</Button>

【讨论】:

这说明了如何创建一个后退按钮,而不是按照 OP 的要求截取浏览器的后退按钮。

以上是关于在 React-router 中拦截/处理浏览器的后退按钮?的主要内容,如果未能解决你的问题,请参考以下文章

ZF_react react-router prompt lazy的实现

vue router拦截器防止重复点击

Vue/React实现路由鉴权/导航守卫/路由拦截(react-router v6)

react-router

react + react-router + redux + ant-Desgin 搭建管理后台 -- 处理登录及默认选中侧边栏(六)

window.open方法被浏览器拦截的处理方式