在React中控制浏览器后退按钮

时间:2020-03-24 20:27:02

标签: reactjs web-applications react-router progressive-web-apps

我想使我的Web应用程序像移动应用程序一样工作。这意味着当用户按下时,他们期望弹出窗口关闭,而不是整个页面都更改。

我的最终目标是做到这一点,以便当模态打开时,“后退”按钮现在将关闭模态,如果他们再次单击它,它将返回。

我尝试了几种方法,尽管关闭它们却始终无法响应。 https://codesandbox.io/s/github/subwaymatch/react-disable-back-button-example-v2

有人在寻找我所寻找的有效版本吗?

4 个答案:

答案 0 :(得分:3)

您可以尝试在URL中使用哈希。 哈希是以井号开头的URL段。在散列之间进行导航通常不会触发任何页面加载,但是仍然可以将条目推入浏览器历史记录,从而使后退按钮关闭模式/弹出窗口。

// www.example.com#modal
window.location.hash // -> "#modal"

您显示和隐藏的模式状态基于window.location.hash

您可以创建类似这样的钩子(仅用于抽象)

function useHashRouteToggle(modalHash) {
  const [isOpen, toggleOpen] = useState(false);

  const toggleOpenModal = (open) => {
    if (open) {
      window.location.assign(modalHash); // navigate to same url but with the specified hash
    } else {
      window.location.replace('#'); // remove the hash
    }
  }

  useEffect(() => { 
    // function for handling hash change in browser, toggling modal open 
    const handleOnHashChange = () => {  
      const isHashMatch = window.location.hash === modalHash;   
      toggleOpen(isHashMatch);  
    };  

    // event listener for hashchange event
    window.addEventListener('hashchange', handleOnHashChange);  

    return () => window.removeEventListener('hashchange', handleOnHashChange);  
  }, [hashRoute]);

  return [isActive, toggleActive];
} 

然后在弹出窗口/模式中使用它。

const [isActive, toggleActive] = useHashRouteToggle('#modal');

const openModal = () => toggleActive(true);

<Modal isShow={isActive} />

这样,您可以满足您的需求而无需修改或覆盖浏览器行为。上面的代码仅是您可以做什么的抽象。您可以根据需要对其进行优化。希望它能给您一些想法。

答案 1 :(得分:2)

为了使后退按钮在模式关闭时起作用,打开模式时需要按一下路线,在onclose时可以使用history.goBack()。也许这个例子会有所帮助。

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  useHistory,
  useLocation,
  useParams
} from "react-router-dom";

export default function ModalGalleryExample() {
  return (
    <Router>
      <ModalSwitch />
    </Router>
  );
}

function ModalSwitch() {
  let location = useLocation();
  let background = location.state && location.state.background;
  return (
    <div>
      <Switch location={background || location}>
        <Route exact path="/" children={<Gallery />} />
        <Route path="/img/:id" children={<ImageView />} />
      </Switch>
      {background && <Route path="/img/:id" children={<Modal />} />}
    </div>
  );
}

const IMAGES = [
  { id: 0, title: "Dark Orchid", color: "DarkOrchid" },
  { id: 1, title: "Lime Green", color: "LimeGreen" },
  { id: 2, title: "Tomato", color: "Tomato" },
  { id: 3, title: "Seven Ate Nine", color: "#789" },
  { id: 4, title: "Crimson", color: "Crimson" }
];

function Thumbnail({ color }) {
  return (
    <div
      style={{
        width: 50,
        height: 50,
        background: color
      }}
    />
  );
}

function Image({ color }) {
  return (
    <div
      style={{
        width: "100%",
        height: 400,
        background: color
      }}
    />
  );
}

function Gallery() {
  let location = useLocation();

  return (
    <div>
      {IMAGES.map(i => (
        <Link
          key={i.id}
          to={{
            pathname: `/img/${i.id}`,
            // This is the trick! This link sets
            // the `background` in location state.
            state: { background: location }
          }}
        >
          <Thumbnail color={i.color} />
          <p>{i.title}</p>
        </Link>
      ))}
    </div>
  );
}

function ImageView() {
  let { id } = useParams();
  let image = IMAGES[parseInt(id, 10)];

  if (!image) return <div>Image not found</div>;

  return (
    <div>
      <h1>{image.title}</h1>
      <Image color={image.color} />
    </div>
  );
}

function Modal() {
  let history = useHistory();
  let { id } = useParams();
  let image = IMAGES[parseInt(id, 10)];

  if (!image) return null;

  let back = e => {
    e.stopPropagation();
    history.goBack();
  };

  return (
    <div
      onClick={back}
      style={{
        position: "absolute",
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        background: "rgba(0, 0, 0, 0.15)"
      }}
    >
      <div
        className="modal"
        style={{
          position: "absolute",
          background: "#fff",
          top: 25,
          left: "10%",
          right: "10%",
          padding: 15,
          border: "2px solid #444"
        }}
      >
        <h1>{image.title}</h1>
        <Image color={image.color} />
        <button type="button" onClick={back}>
          Close
        </button>
      </div>
    </div>
  );
}

作为参考,请检查react router modal gallery example

答案 2 :(得分:1)

实际上,我相信后退功能对于用户体验很有用,但对于模式打开/关闭,您是正确的。浏览器的“后退”按钮应同时关闭台式机和移动设备中的模式。我为您提供了两个助手功能,一个用于中和浏览器后退按钮,然后运行您自己的功能 一个用于恢复浏览器后退按钮。在打开模态时使用neutralizeBack函数,在打开的模态关闭时使用revivalBack函数。使用第二种方式回到我对浏览器后退按钮功能的用户体验的态度。

  • neutralizeBack应该运行一个回调函数。这个回调函数就是您想要做的:

    const neutralizeBack = callback => {
      window.history.pushState(null, "", window.location.href);
      window.onpopstate = () => {
        window.history.pushState(null, "", window.location.href);
        callback();
      };
    };
    
  • revivalBack应该在您希望恢复浏览器后退按钮功能时运行:

    const revivalBack = () => {
      window.onpopstate = undefined;
      window.history.back();
    };
    

用法示例:

handleOpenModal = () =>
  this.setState(
    { modalOpen: true },
    () => neutralizeBack(this.handleCloseModal)
  );

handleCloseModal = () =>
  this.setState(
    { modalOpen: false },
    revivalBack
  );

答案 3 :(得分:0)

这是我的 Dimitrij Agal answer 版本,包含实际工作代码而不仅仅是伪代码。它使用 "react-router-dom": "^6.0.0-beta.0"

import { useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";





export function useHashRouteToggle(hash) {

  const navigate = useNavigate();
  const location = useLocation();

  const [isActive, setIsActive] = useState(false);

  const toggleActive = (bool) => {
    if (bool !== isActive) {   // needed if there are multiple modals with close-on-esc-keyup in the same page
      if (bool) {
        navigate(location.pathname + "#" + hash)
      } else {
        navigate(-1);
      }
      setIsActive(bool);
    }
  }

  useEffect(() => { 
    const handleOnHashChange = () => {  
      setIsActive(false);
    };  

    window.addEventListener('hashchange', handleOnHashChange);  
    
    return () => window.removeEventListener('hashchange', handleOnHashChange);  
  });

  return [isActive, toggleActive];
} 

你像这样使用它:

const [showModalDelete, setShowModalDelete] = useHashRouteToggle("delete")

// ...

<CoreModal
  isActive={showModalDelete}
  setIsActive={setShowModalDelete}
  title={t("deleteProduct")}
  content={modalContent}
/>

但是至少有两个问题:

  • 如果用户在关闭模态后使用“前进”按钮,他/她将不得不按两次“后退”按钮。
  • 我尝试将模态的初始状态作为参数传递,以防程序员想要在打开时启动模态 (isActive === true) 但我无法使其工作,虽然我没有探索这种可能性很大,因为我所有的模态都是以关闭状态开始的。

任何反馈将不胜感激