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

Intercept/handle browser's back button in React-router?

本文关键字:浏览器 按钮 处理 React-router      更新时间:2023-09-26

我正在使用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"/>} />

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

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

我发现了这个:

window.onpopstate = this.onBackButtonEvent;

,但这是每次状态改变时调用(不只是在后退按钮事件)

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

import { browserHistory } from 'react-router';
componentDidMount() {
    this.onScrollNearBottom(this.scrollToLoad);
    this.backListener = browserHistory.listen((loc, action) => {
      if (action === "POP") {
        // Do your stuff
      }
    });
  }
componentWillUnmount() {
    // Unbind listener
    this.backListener();
}

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

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, ])

我是这样做的:

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})
      }
    }
  }

感谢大家的帮助lol

Hooks示例

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

我不使用历史。听,因为它不影响状态

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

这个问题的大多数答案要么使用过时版本的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.
                }
           }
    })
}

可以在这里找到一个更完整的示例和解释。

第三版。React Router API的x版本有一组实用程序,你可以使用它们在"返回"按钮事件注册到浏览器的历史记录之前公开该事件。首先必须将组件封装在withRouter()高阶组件中。然后你可以使用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);

使用钩子。我已经把@Nicolas Keller的代码转换成typescript

  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]);

在NextJs中我们可以使用beforePopState函数做我们想做的事情例如关闭模态或显示模态或检查后端地址并决定做什么

const router = useRouter();
useEffect(() => {
    router.beforePopState(({ url, as, options }) => {
        // I only want to allow these two routes!
        if (as === '/' ) {
            // Have SSR render bad routes as a 404.
             window.location.href = as;
            closeModal();
            return false
        }
        return true
    })
}, [])

我使用withouter hoc为了获得历史道具,只是写一个componentDidMount()方法:

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

用于在react功能组件中返回浏览器时发出警告。执行以下步骤

  1. 声明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)
      }
    }
    

    }

  • 在componentwillmount unsubscribe onBackButtonEvent Function

  • 最终代码看起来像这样

    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
    

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

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

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

    将这两行添加到componentDidMount()中。

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

    输入componentDidMount()

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

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

    如果你在react-router中使用BrowserRouter(在react-router v4中不可用),如上所述,你可以使用'POP'动作来拦截浏览器的返回按钮。

    但是,如果使用HashRouter推送路由,上述解决方案将不起作用。原因是当你点击浏览器后退按钮或从组件中推送路由时,hash路由器总是被'POP'动作触发。你也不能用window来区分这两个动作。Popstate或历史。只是听。

    即将发布的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);
    

    您可以使用" withouter " HOC和this.props.history.goBack

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