如何在 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 Component
或window.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 方式(点击两次)。删除表单中注释的 <Prompt .../>
并将 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-router
和react-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-router
和react-router-dom
这两个包。这是最简单的逻辑,如果你的表单很脏,那么你应该提示用户并阻止他们离开页面。
【讨论】:
【参考方案3】:您可以使用 react-router API 创建它,也可以使用已经可用的包,例如 React Navigation Prompt。
它在底层使用 react-router
【讨论】:
【参考方案4】:我相信答案是使用阻塞标志。看到这个:https://reactrouter.com/web/example/preventing-transitions
【讨论】:
以上是关于如何在 react-router-dom 中实现像 vue-router 这样的路由器离开保护?的主要内容,如果未能解决你的问题,请参考以下文章