React-router V6 拦截 路由跳转

目标实现效果:拦截路由变化做自定义处理,比如在一个表单尚未填写完成,用户就要离开当前页面此时需要给用户做一个提醒,如下图所示
React-router V6 拦截 路由跳转_第1张图片

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

v5 版本实现路由拦截

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

      // 文档:https://v5.reactrouter.com/core/api/Prompt
       {
          // 做一些拦截操作 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 的办法来去除 默认询问弹框
    React-router V6 拦截 路由跳转_第2张图片
  • 完整代码

    import { History, Transition } from 'history'
    import { useContext, useEffect } from 'react'
    import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom'
    
    type ExtendNavigator = Navigator & Pick
    
    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(null)
        useBlocker(tx => {
          setOpen(true)
          blockRef.current = tx
        }, when)
        return (
           setOpen(false)}
            onCancel={() => blockRef.current?.retry()}
            onOk={() => setOpen(false)}
          >
            

    You have unsaved change, exit without saving?

    ) }

注意

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

你可能感兴趣的:(react-router拦截器)