如何在 react-router-dom 中实现像 vue-router 这样的路由器离开保护?

Posted

技术标签:

【中文标题】如何在 react-router-dom 中实现像 vue-router 这样的路由器离开保护?【英文标题】:How to implement router leave guard like vue-router in react-router-dom? 【发布时间】:2021-09-27 15:09:18 【问题描述】:

我需要询问用户是否在反应项目中提交表单,例如在 vue-router beforeRouteLeave 守卫中:

<template>
  <div>
    <input
      type="text"
      name="name"
      v-model="name"
      placeholder="input some text"
    />
    <router-link to="/">go back or click goback menu in browser </router-link>
  </div>
</template>

<script>
import  MessageBox  from "element-ui";

export default 
  data() 
    return 
      name: "",
    ;
  ,
  methods: 
    http() 
      return new Promise((resolve, reject) => 
        setTimeout(() => 
          const isOk = Math.random() > 0.5;
          isOk && resolve( ok: true );
          !isOk && reject(new Error("server 503"));
        , 2000);
      );
    ,
  ,
  async beforeRouteLeave(to, from, next) 
    if (this.name) 
      // form has input
      try 
        // it's better to use UI modal than window.confirm
        await MessageBox.confirm("do u want to submit?", "tips", 
          confirmButtonText: "yes",
          cancelButtonText: "no",
          type: "warning",
        );
        const res = await this.http(); //submit form
        console.log(res);
        // http success,let it go
        res.ok && next();
       catch (error) 
        next(false);
      
     else 
      next();
    
  ,
;
</script>

demo in codesandbox online by vue-router

当用户单击浏览器菜单中的后退和前进按钮时,此功能效果很好,并且程序化导航也可以正常工作。如何在 react-router-dom 中实现相同的要求?

我基于deechris27's answer尝试过这种方式,但是它不能作为vue-router使用。

我的密码:

import React,  useState, useEffect  from 'react'
import  useHistory  from 'react-router-dom'
import  Modal  from 'antd'
import  ExclamationCircleOutlined  from '@ant-design/icons'

const  confirm  = Modal
function Confirm() 
  return new Promise((resolve, reject) => 
    confirm(
      title: 'Do you want to save data?',
      icon: <ExclamationCircleOutlined />,
      onOk() 
        resolve( ok: true )
      ,
      onCancel() 
        reject(new Error('cancel'))
      ,
    )
  )


function Users() 

  const http = () => 
    console.log("I'll show up on submit")
    return new Promise((resolve) => 
      setTimeout(() => 
        resolve( ok: true )
      , 2000)
    )
  

  const history = useHistory()
  const [input, setInput] = useState('')
  const changeInput = (event) => 
    const  value  = event.currentTarget
    setInput(value)
  

  useEffect(() => 
    return history.listen(async (location, action) => 
      console.log(location, action)
      if (action === 'POP') 
        // const ok = window.confirm('do u want to submit form?')
        // url has change when confirm modal show
        try 
        await Confirm()
        // url has change when confirm modal Component show

        // send http request
        const res = await http()
        console.log(res)
       catch (error) 
        // http error or cancel modal
      
      
    )
  , [history])

  const handleSubmit = async (e) => 
    console.log('**********')
    e.preventDefault()
    if (input) 
      // const ok = window.confirm('Do u want submit?')
      try 
        const  ok  = await Confirm()
        console.log(ok)
        if (ok) 
          console.log('send http')
          const res = await http() // answer is yet false
          console.log(res)
          res.ok && history.push('/')
        
       catch (error) 
        // http error or cancel modal
      
     else 
      history.push('/')
    
  
  return (
    <form onSubmit=handleSubmit>
      <input name="name" value=input onChange=changeInput placeholder="input some text" />
      <br />
      <button type="submit">submit</button>
    </form>
  )


export default Users

当自定义Confirm Componentwindow.confirm显示时,url已经变成了,所以这种方式不能停止导航。提示有同样的问题。

【问题讨论】:

【参考方案1】:

你可以在 React 中使用 window.confirm 做同样的事情。使用state 值以反应方式进行操作会有一个问题; 定时 2 个异步操作:setState 和基于 answer 值的异步函数调用。

下面的例子:

App.js

import "./styles.css";
import  Switch, Route  from "react-router-dom";
import Test from "./Test";
import Leave from "./Leaving";

export default function App() 
  return (
    <div className="App">
      <Switch>
        <Route exact path="/prompt" component=Leave />
      </Switch>
      <Test />
    </div>
  );

Test.js

import React,  useState  from "react";
import  Prompt, useHistory  from "react-router-dom";

function Test() 

  const [answer, setIsAnswer] = useState(false);

  const history = useHistory();

  const http = () => 
    console.log("I'll show up on submit");
    return new Promise((resolve) => 
      setTimeout(() => 
        resolve( ok: true );
      , 200);
    );
  ;


  const handleSubmit = async (e) => 
    console.log("**********");
    e.preventDefault();
    e.target.reset();
    setIsAnswer(true);  // setting state is async
    history.push("/prompt");
    answer && (await http()); // answer is yet false
  ;

  return (
    <div className="Test">
      <h1>Hello Jack</h1>
      <form onSubmit=(e) => handleSubmit(e)>
          <Prompt when=true message=(_) => "Do you want submit form?" /> // **when**'s value should ideally depend on answer value, harcoded for example purpose.
        <button>Click</button>
      </form>
    </div>
  );


export default Test;

只有当你第二次点击时,React 的提示方式才会起作用,因为setIsAnswer 是异步的。

您可以使用相同的window.confirm onSubmit。

const handleSubmit = async (e) => 
    console.log("**********");
    e.preventDefault();
    e.target.reset();
    const answer = window.confirm("Do you want submit form?");
    answer && history.push("/prompt");
    answer && (await http());
  ;
    
     

我在代码框https://codesandbox.io/s/charming-dew-u32wx 中为您创建了一个工作示例

在上面的沙箱中,handleSubmit 是纯 JS 方式。 handleSubmit2 是 React 方式(点击两次)。删除表单中注释的 &lt;Prompt .../&gt; 并将 onSubmit 函数更改为 handleSubmit 然后 handleSubmit2 以查看两者的作用。

解释

任何依赖 state 值的反应模块或方法对您的场景没有帮助,您需要在提示确认后进行 HTTP 调用。 window.comfirm 是阻塞代码,answer 的使用状态是非阻塞的。如果只是在导航之前提示,那么您可以使用上述反应或建议的任何其他反应方法。

响应 cmets 的更新:

您可以通过添加以下代码来阻止浏览器返回按钮导航。

const history = useHistory();

  useEffect(() => 
    return () => 
      if (history.action === "POP") 
       return false;
      
   ;
 , [history]);

在您的 Prompt 中通过回调

<Prompt
    when=(location, action) => 
      if (action === "POP") 
        //
      
    
    message=(_) => "Are you sure, you want to go back?"
/>

我已使用此浏览器后退按钮场景更新了 codesandbox 中的 Leaving.js。

【讨论】:

我已在此处和 codesanbox 中更新了我的答案。几天前我回答了后退按钮问题。 ***.com/questions/68441641/… 我已经更新了问题,为什么我的代码不起作用?无法停止导航。【参考方案2】:

这是使用react-routerreact-router-dom 包阻止用户更改路由的最简单示例。

import React from "react";
import  BrowserRouter, Route, NavLink  from "react-router-dom";
import  Prompt  from "react-router";
import "./App.css";

function App() 
  const [dirty, setDirty] = React.useState(false);
  return (
    <BrowserRouter>
      <div className="App">
       <input onChange=() => setDirty(true) /> dirty ? "dirty" : "clean"
       <br />
       <NavLink to="/test">Leave</NavLink>
       <br />
       <Route path="/test" component=() => "Has left" />
       <Prompt message="Are you sure you want to go to /test?" />
     </div>
    </BrowserRouter>
  );

export default App;

你只需要安装react-routerreact-router-dom这两个包。这是最简单的逻辑,如果你的表单很脏,那么你应该提示用户并阻止他们离开页面。

【讨论】:

【参考方案3】:

您可以使用 react-router API 创建它,也可以使用已经可用的包,例如 React Navigation Prompt。

它在底层使用 react-router

【讨论】:

【参考方案4】:

我相信答案是使用阻塞标志。看到这个:https://reactrouter.com/web/example/preventing-transitions

【讨论】:

以上是关于如何在 react-router-dom 中实现像 vue-router 这样的路由器离开保护?的主要内容,如果未能解决你的问题,请参考以下文章

在颤动中实现像香奈儿应用程序一样的自定义滚动?

facebook app 如何实现可拖动的浮动Button

有没有办法在 JAVAFX 上实现像“渲染”这样的属性?

有没有办法在 JAVAFX 上实现像“渲染”这样的属性?

如何在react-router-dom中嵌套路由[重复]

如何在 react-router-dom 中正确定义后备路由