指标实现成果:拦挡路由变动做自定义解决,比方在一个表单尚未填写实现,用户就要来到以后页面此时须要给用户做一个揭示,如下图所示

先说一下背景常识:React-router 是由三个库一起组成的 history、react-router、react-router-dom 咱们平时须要用到的是 react-router-dom

v5 版本实现路由拦挡

  • 以前在应用 v5 版本时,是这样实现路由拦挡的

      // 文档:https://v5.reactrouter.com/core/api/Prompt  <Prompt    when={boolean} // 组件何时激活    message={(location, action) => {      // 做一些拦挡操作 location 要返回的路由,此时能够先保留下来后续应用      // return false 勾销跳转 比方此时弹起一个自定义弹窗,      // return true 容许跳转    }}  />

v6 版本实现

  • v6 版本没有了 Prompt 组件,Google 搜寻之后找到了这个 stackoverflow v6 beta 时提供了两个 hooks useBlocker/usePrompt 能够用来实现路由拦挡,然而到正式版的时候这两个 hook 就被移除了,这个 issue 外面有探讨,这里有人找出了解决方案就是把删除的这两个 hooks 再加回去
  • 其实路由拦挡性能次要是用到了 history 库外面的 block 办法,这里是相干代码
  • histoy block 文档

    history.block will call your callback for all in-page navigation attempts, but for navigation that reloads the page (e.g. the refresh button or a link that doesn't use history.push) it registers a beforeunload handler to prevent the navigation. In modern browsers you are not able to customize this dialog. Instead, you'll see something like this (Chrome):
  • 简略的翻译下就是 histoy.block 会阻止页面中的所有导航并调用callback,然而间接敞开 tab 页或是刷新会注册 beforeunload 事件继而触发浏览器的默认询问弹窗,不反对去除默认弹框,我上面采纳了一种 hack 的方法来去除 默认询问弹框
  • 残缺代码

    import { History, Transition } from 'history'import { useContext, useEffect } from 'react'import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom'type ExtendNavigator = Navigator & Pick<History, 'block'>export function useBlocker(blocker: (tx: Transition) => void, when = true) {  const { navigator } = useContext(NavigationContext)  useEffect(() => {    if (!when) return    // 如不须要刷新页面或敞开tab时勾销浏览器询问弹窗,上面的绑定事件则不须要    window.addEventListener('beforeunload', removeBeforeUnload)    const unblock = (navigator as any as ExtendNavigator).block(tx => {      const autoUnblockingTx = {        ...tx,        retry() {          unblock()          tx.retry()        },      }      blocker(autoUnblockingTx)    })    // 因为无奈间接 remove history 库中绑定的 beforeunload 事件,只能本人在绑定一个 beforeunload 事件(在原事件之前),触发时调用 unblock    //     function removeBeforeUnload() {      unblock()    }    return () => {      unblock()      window.removeEventListener('beforeunload', removeBeforeUnload)    }  }, [when])}
  • 应用 useBlocker

    export default function UnsavedPrompt({ when }: Iprops): JSX.Element {    const [open, setOpen] = useState(false)    const blockRef = useRef<any>(null)    useBlocker(tx => {      setOpen(true)      blockRef.current = tx    }, when)    return (      <Modal        open={open}        toggle={() => setOpen(false)}        onCancel={() => blockRef.current?.retry()}        onOk={() => setOpen(false)}      >        <p className='text-center text-light-700 text-sm'>          You have unsaved change, exit without saving?        </p>      </Modal>    )  }

留神

  • 书写本文的工夫是 2022-08-11 ,react-router/react-router-dom 的最新版本为 6.3.0,后续可能随着 react-router-dom 的降级可能还会加回来该性能,上述代码仅供参考