likes
comments
collection
share

时隔一年,react-router 终于重新加回了这个功能-跳转拦截

作者站长头像
站长
· 阅读数 18

前言

路由的跳转拦截,在一些表单页中尤为常见。场景:用户输入了一些信息后但未提交,为了防止用户误点击某个跳转链接导致所填的表单信息丢失,跳转之前会弹出一个提示,如 “信息未提交,请确认是否离开” 等字样。

事件经过

react-router v6 之前有提供一个 <Prompt /> 组件来拦截路由的跳转。而在 v6 中却不支持此功能了!社区一片哀嚎,经查在 v6 实验阶段是有此功能的,但在 v6.0.0 beta-7 中删除了此功能。在这个 issue 链接 中每个人都强烈要求需要此功能,但官方回复会在不久的将来添加,有需要的可以回退到 v5,基本所有人都表示 👎。然而这一等就是一年多,很多人这期间在 issue 中表示非常失望,这个不久的将来 到底还有没有了?终于在 v6.7.0 重新加入了此功能,原来不久的将来约等于一年。

v6 之前的版本如何拦截

在你需要拦截的页面添加 <Prompt /> 组件,我们在输入框中有值,且进行跳转页面时进行拦截。当 when 为 true 时,执行跳转会触发拦截操作,message 为提示。

function Home() {
  const [value, setValue] = useState("");

  return (
    <div>
      <Link to="/about" />
      <input value={value} onChange={e => setValue(e.target.value)} />
      <Prompt when={!!value} message="确认要离开吗" />
    </div>
  );
}

弹出的提示进行自定义,when 可以设置为一个函数,参数为 locationaction(行为),返回 string | boolean。返回 stringtrue 就是直接离开了。

function Home() {
  const [value, setValue] = useState("");
  const history = useHistory();

  return (
    <div>
      <Link to="/about" />
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <Prompt
        when={!!value}
        message={(location, action) => {
          Modal.confirm({
            message: "确定要离开吗",
            onOk: () => {
              history.push(location.pathname);
            },
          });
          return false;
        }}
      />
    </div>
  );
}

v6.7.0+ 如何拦截

在这个版本中提供了一个 unstable_useBlocker 钩子,但在文档中是没有的(艹),需要自己去 examples 里找。useBlocker 传入的参数类似于上面的 when

useBlocker 的参数为 boolean函数返回 boolean,函数的类型如下:

(args: {
    currentLocation: Location;
    nextLocation: Location;
    historyAction: HistoryAction;
}) => boolean

blocker 里的 stateblocked 时,说明当前正在进行拦截,此时弹出一个 Modal,点击确认就是进行跳转,点击取消就是不跳转。blocked 中还能获取到 location,可以根据你的需要来使用。

import { unstable_useBlocker as useBlocker } from "react-router-dom";

function Home() {
  const [value, setValue] = useState("");
  const blocker = useBlocker(!!value);
  
  useEffect(() => {
    if (blocker.state === "blocked") {
      Modal.confirm({
        message: "确认离开吗",
        onOk: () => {
          blocker.proceed?.();
        },
        onCancel: () => {
          blocker.reset?.();
        },
      });
    }
  }, [blocker]);

  return (
    <div>
      <Link to="/about" />
      <input value={value} onChange={(e) => setValue(e.target.value)} />
    </div>
  );
}

吐槽

最后,就在这里吐槽一下吧。文档很烂,不支持搜索!且 v6.4 的后续版本中加入了很多 api,例如 actionloader 的概念,跟数据请求有关。但是我觉得很难用,路由库就应该只专注路由,应该跟它们的 remix 框架的发展有关吧。官方的权限控制例子也是不好用,很麻烦。下个文章会写在 v6 中如何优雅简单的管理权限。