【问题标题】:Controlling Browser Back button in React在 React 中控制浏览器的后退按钮
【发布时间】:2020-03-24 20:27:02
【问题描述】:

我想让我的网络应用像移动应用一样工作。这意味着当用户按下返回时,他们希望弹出窗口关闭,而不是整个页面都发生变化。

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

我尝试了几种方法,虽然很接近,但它们的响应始终不一致。 https://codesandbox.io/s/github/subwaymatch/react-disable-back-button-example-v2

任何拥有我正在寻找的经过验证的工作版本的人?

【问题讨论】:

  • 您不能只覆盖此处提到的浏览器按钮的行为:stackoverflow.com/questions/24033040/….
  • 找到我认为类似的东西?你想要达到的目标,但它是反应原生的reactnavigation.org/docs/modal
  • @AbeCaymo ``` window.onpopstate = e => { ... } ``` 我的主要问题是一致地删除事件处理程序。它确实应该在卸载时删除
  • 我不建议你对后退按钮这样做,用户期望后退按钮是“上一页”,你的用户可能不会考虑点击浏览器的后退按钮来关闭一个模态!!!
  • 也许有人认为,你的问题不是很重要,但我相信这个问题在很多项目中都会出现几次。我为这篇精彩的帖子投了赞成票。干得好。

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


【解决方案1】:

实际上,我相信后退功能对用户体验很有用,但对于模态 open/close 你是对的。浏览器的后退按钮应关闭桌面和移动设备中的模式。我建议您编写两个辅助函数,一个用于neutralize 浏览器返回按钮,然后运行您自己的功能一个用于revival 浏览器返回按钮.当模态打开时使用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
  );

【讨论】:

    【解决方案2】:

    您可以尝试在您的网址中使用哈希。 Hash 是以井号标签开头的 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);  
      }, [modalHash]);
    
      return [isActive, toggleActive];
    } 
    

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

    const [isActive, toggleActive] = useHashRouteToggle('#modal');
    
    const openModal = () => toggleActive(true);
    
    <Modal isShow={isActive} />
    

    这样,您可以在不修改或覆盖浏览器行为的情况下满足您的需求。上面的代码只是对您可以做什么的抽象。您可以根据自己的需要对其进行细化。希望能给你一些想法。

    【讨论】:

    • 感谢您提供的精美解决方案。你能告诉我 hashroute 应该是什么吗?我面临错误“未定义‘hashRoute’”
    • 啊,我的错,现在才注意到变量应该是“modalHash”,而不是“hashRoute”。将编辑帖子。
    【解决方案3】:

    为了使后退按钮在模态关闭时起作用,您需要在打开模态时推送一条路线,并且在关闭时您可以使用 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

    【讨论】:

      【解决方案4】:

      这是我的 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) 但我无法使其工作,尽管我没有探索这种可能性很大程度上是因为我所有的模态都是关闭的。

      任何反馈将不胜感激

      【讨论】:

        【解决方案5】:
        if (isOpen) {
          // push to history when modal opens
          window.history.pushState(null, '', window.location.href)
          
          // close modal on 'back'
          window.onpopstate = () => {
            window.onpopstate = () => {}
            window.history.back()
            setIsOpen(false)
          }
        }
        
        return <Modal open={isOpen} />
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2010-10-28
          • 1970-01-01
          • 2023-03-07
          • 2016-03-15
          • 2013-03-02
          • 2016-06-09
          • 1970-01-01
          • 2017-10-03
          相关资源
          最近更新 更多