react-router防止路由更改并关闭后退按钮上的弹出窗口

IT技术 javascript reactjs react-router browser-history
2022-07-22 00:38:14

在我的react应用程序中,我在其中一个页面中有一个弹出窗口。现在,当弹出窗口打开并且用户按下浏览器的后退按钮时,用户将被带到上一页,而不是我想简单地关闭弹出窗口。

我已经遵循了很多与此相关的解决方案,但似乎都没有。下面的代码允许我关闭弹出窗口,但它的作用是在弹出窗口所在页面的历史记录中推送一个额外的 url。因此,一旦弹出窗口关闭,我需要按两次才能真正进入上一页。这会给用户带来不好的体验,因此我不想将它添加到我的应用程序中。

const myPopState = (event) => {
    window.history.go(1);
    closeModal();
};

useEffect(() => {
    window.history.pushState(null, null, location.href);
    window.addEventListener("popstate", myPopState);
    return () =>  window.removeEventListener("popstate", myPopState);
}, []);

PS:我不想为弹出屏幕设置单独的路线。已经问了一个类似的问题,但我不愿意为弹出窗口设置一个独特的路线,所以问这个。

2个回答

现场演示

首先创建这个函数文件:

import { useEffect, useState } from 'react'
import _ from 'lodash'

const listOfListener = {}

function onPopState(event) {
  _.forEach(listOfListener, (l) => {
    tryIt(() => l(event))
  })
}

export const tryIt = (fun, defaultVal) => {
  try {
    return fun()
  } catch (e) {
    return _.isFunction(defaultVal) ? defaultVal() : defaultVal
  }
}

export const getSafe = (fun, defaultVal) => tryIt(fun, defaultVal)

const buttonDelay = 1000

export default function useOpenWithBrowserHistory(uniq, defaultValue) {
  const [openDelay, setOpenDelay] = useState(false)
  const [closeDelay, setCloseDelay] = useState(false)
  const [open, setOpen] = useState(false)
  const [element, setElement] = useState(undefined)

  useEffect(() => {
    if (!listOfListener[uniq]) {
      listOfListener[uniq] = function (event) {
        const state = event.state
        const data = getSafe(() => state[uniq], undefined)
        if (!state || data !== true) {
          setOpen(false)
          return
        }
        if (data === true) {
          setOpen(true)
        }
      }
    }
    window.onpopstate = onPopState
    if (defaultValue) {
      handleOpenClick()
    }

    return () => {
      tryIt(() => {
        delete listOfListener[uniq]
      })
    }
  }, [])

  const handleOpenClick = (open = true) => {
    if (openDelay === true) return
    setOpenDelay(true)

    const openProcess = () => {
      const data = {}
      data[uniq] = true
      // eslint-disable-next-line no-undef
      history.pushState(data, null, location.href)
      setOpen(true)

      setTimeout(() => {
        setOpenDelay(false)
      }, buttonDelay)
    }

    const target = tryIt(() => open.target)

    if (target) {
      setElement(target)
      scrollToElement(target)
      setTimeout(() => {
        openProcess()
      }, 600)
      return
    }
    openProcess()
  }

  const handleCloseClick = () => {
    if (closeDelay === true) return
    setCloseDelay(true)
    if (element) {
      window.onscroll = () => {
        scrollToElement()
      }
      setTimeout(() => {
        window.onscroll = () => {}
        setElement(undefined)
      }, 1000)
    }
    window.history.back()

    setTimeout(() => {
      setCloseDelay(false)
    }, buttonDelay)
  }

  function scrollToElement(target = element) {
    tryIt(() =>
      target.scrollIntoView({
        behavior: 'smooth',
        block: 'center',
        inline: 'nearest'
      })
    )
  }

  const handleSetOpen = (open) => {
    if (open) {
      handleOpenClick(open)
      return
    }
    handleCloseClick()
  }

  return [open, handleSetOpen, handleOpenClick, handleCloseClick]
}

然后在您的组件中,您可以像这样使用:

function App() {
    //handleOpen and handleClose it's direct handler
    const [open, setOpen, handleOpen, handleClose] = useOpenWithBrowserHistory(
        "uniq-key"
    );
    return (
        <div className="App">
            <button onClick={handleOpen}>open</button>
            {open && (
                <div class="modal">
                    You can close this modal with browser back button or click
                    Close button
                    <br />
                    <br />
                    <button onClick={handleClose}> close</button>
                </div>
            )}
        </div>
    );
}

或者你可以在这个库reacthelper中使用 useOpenWithBrowserHistory 函数

现场演示

将弹出窗口保留为单独的组件并将其导入您想要的任何文件中。然后只需使用状态来管理弹出框的隐藏/显示。

看看我对这个问题的回答。

https://stackoverflow.com/a/66873913/5782438