首页 > 解决方案 > React Router v5.2 - 使用 createBrowserHistory 和 history.block 阻止路由更改

问题描述

我的应用程序有两个页面:Step1Step2. Step1有一个复选框,如果选中则阻止导航,还有一个Step2单击时导航到的下一步按钮。Step2有一个“上一个”按钮,单击该按钮可返回到该Step1按钮。

演示链接

根据本教程,如果选中了复选框,我将使用对象的block方法createBrowserHistory来阻止路由更改Step1

const unblock = useRef();

  useEffect(() => {
    unblock.current = history.block((tx) => {
      if (block.current.checked) {
        const promptMessage = "Are you sure you want to leave?";

        if (window.confirm(promptMessage)) {
          unblock.current();
          tx.retry();
        }
      } else {
        console.log("tfgx");
        unblock.current();
        tx.retry();
      }
    });
  }, []);

我还必须将低级<Router>(不是<BrowserRouter>)中的历史道具设置为createBrowserHistory对象,如下所示:

<Router history={createBrowserHistory()}>
...
</Router>

但这会阻止路线被正确渲染。我认为这可能与<Switch>无法正确读取位置对象有关。如果我使用<BrowserRouter>,则位置对象如下所示{pathname: "/step1", ... key: "7sd45"}:但是当我使用时<Router={createBrowserHistory()}>,位置对象看起来像这样{action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}}。(我也收到了警告You cannot change <Router history>。)

如果选中“阻止导航”复选框,我想要的结果是阻止导航,否则取消阻止。如果导航畅通时位置发生变化,我希望正确呈现相应的路线。

React Router v5 文档中关于 createBrowserHisory 的部分很少,并且没有很多使用它的示例,所以如果有人能对此有所了解,我将不胜感激。


编辑:传递location.location<Switch>似乎可以解决它(更新的演示)。但是,如果我useLocation在内部调用Step1并打印结果(第 17-18 行),我会得到{pathname: "/step1", ... key: "7sd45"}而不是{action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}}. 为什么是这样?

此外,如果用户在导航被阻止时尝试去另一个位置,我的自定义提示会按预期显示(“您确定要离开”,带有“确定”和“取消”按钮)。但是,如果他们通过单击取消取消此操作,则会出现浏览器自己的对话框 -

在 Chrome 中:

在此处输入图像描述

在火狐中:

在此处输入图像描述

在我的提示被解除后是否可以禁止浏览器提示?

标签: javascriptreactjsreact-routerreact-router-dom

解决方案


路由器上下文的history对象也有一个块功能,但它的工作方式略有不同。它需要一个消耗locationaction参数的回调。

history.block((location, action) => {...});

从回调返回false阻止导航转换,返回true允许转换通过。

React.useEffect(() => {
  const unblock = history.block((location, action) => {
    if (checkBlockingCondition) {
      return window.confirm("Navigate Back?");
    }
    return true;
  });

  return () => {
    unblock();
  };
}, []);

或者,react-router-dom 建议使用该Prompt组件有条件地阻止路由转换。您的代码非常接近他们的防止转换示例。

更新您的最后一个代码框:

  1. 使用阻塞状态与反应参考,以便提示重新呈现并重新评估条件。
  2. 渲染一个Prompt组件。
  3. 阻止默认的表单提交动作,即阻止页面重新加载。

代码

import {
  BrowserRouter as Router,
  Prompt, // <-- import Prompt
  Redirect,
  Switch,
  Route,
  useLocation
} from "react-router-dom";

const Step1 = ({ id, history }) => {
  const [isBlocking, setIsBlocking] = useState(false);

  return (
    <form
      id={id}
      onSubmit={(e) => {
        e.preventDefault(); // <-- prevent default form action, i.e. page reload
        history.push("/step2");
      }}
    >
      <label>
        Block navigation
        <input
          type="checkbox"
          onChange={(e) => setIsBlocking(e.target.checked)}
        />
      </label>
      <br />
      <br />
      <button type="submit">Next</button>
      <Prompt
        when={isBlocking} // <-- blocking condition
        message="Are you sure you want to leave?"
     />
    </form>
  );
};

编辑 react-router-v5-2-blocking-route-change-with-createbrowserhistory-and-history


推荐阅读