目录
- 1,简单实现
- 1.1,监听
- 1.2,控制跳转
- 2,全局封装
- 3,阻止跳转
vue-router 中的导航守卫 router.beforeEach 有2个作用:
- 监听路由跳转;
- 控制路由是否可以跳转。
在 React 中可以模拟实现。
1,简单实现
1.1,监听
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import RouteGuard from "./RouteGuard";
import News from "./News";
import Goods from "./Goods";export default function App() {let count = 0;return (<Router><Link to="/">首页</Link><Link to="/news">新闻页</Link><Link to="/goods">商品页</Link><RouteGuardonRouteChange={(prevLocation, location, action, unListen) => {console.log("prevLocation", prevLocation);console.log("location", location);console.log("action", action);if (count++ === 3) {unListen(); // 监听3次后,取消监听}}}><Route path="/news" component={News}></Route><Route path="/goods" component={Goods}></Route></RouteGuard></Router>);
}
RouteGuard.jsx
import React, { useEffect } from "react";
import { withRouter } from "react-router-dom";function RouteGuard(props) {useEffect(() => {const unListen = props.history.listen((location, action) => {if (props.onRouteChange) {props.onRouteChange(props.location, location, action, unListen);}});}, []);return props.children;
}export default withRouter(RouteGuard);
路由信息中的 history
,还有一个 listen
属性,用于监听地址变化。当地址变化时就会触发传递的回调函数,
1,listen
执行时间点:即将跳转时。
2,回调函数有2个参数:
location
,记录当前(目标)路由信息。action
,取值PUSH
|POP
|REPLACE
,表示进入路由的方式。
注意,点击浏览器的返回前进按钮,也会触发。
3,listen
返回值也是一个函数,用于取消监听。
1.2,控制跳转
通过设置阻塞器来实现。在 RouteGuard.jsx 中添加一行代码:
function RouteGuard(props) {useEffect(() => {// ...props.history.block("确定要跳转吗");}, []);return props.children;
}
此时在路由跳转时,会有一个 window.confirm
弹窗,确认会跳转,取消不会跳转路由。
原理:
这个阻塞器,会在 Router
组件的 getUserConfirmation
属性中使用,
// getUserConfirmation 的默认值
<RoutergetUserConfirmation={(msg, callback) => {callback(window.confirm(msg));}}
>
其中,msg
参数是 props.history.block(msg)
传递的字符串。
通过传递给回调函数 callback()
的参数来决定是否阻塞。所以,如果直接写 callback(true)
表示始终允许,此时 props.history.block
就“失效”了。
注意,
getUserConfirmation
依赖props.history.block()
的设置才会生效,如果没有添加它,callback(false)
也不会阻塞!
流程:先调用阻塞函数 props.history.block
得到阻塞消息,再调用 getUserConfirmation
的 callback
来决定是否阻塞。
2,全局封装
可以封装一个全局的拦截器,来实现类似 vue-router 的全局路由守卫。
全局路由守卫:RouteGuard.jsx
import React, { useEffect } from "react";
import { BrowserRouter as Router, withRouter } from "react-router-dom";// 方便传递,因为路由信息始终是一个
let prevLoaction, nextLocation, action, unBlock;function _GuardHelper(props) {useEffect(() => {// 添加监听器const unListen = props.history.listen((location, action) => {if (props.onRouteChange) {props.onRouteChange(props.location, location, action, unListen);}});// 添加阻塞unBlock = props.history.block((location, ac) => {prevLoaction = props.location;nextLocation = location;action = ac;return ""; // 阻塞 msg 为空});return () => {unListen();unBlock();};}, []);return null;
}const GuardHelper = withRouter(_GuardHelper);/*** onRouteChange 监听路由变化* onBeforeChange 阻塞路由* @param {*} props* @returns*/
export default function RouteGuard(props) {const handleConfirm = (msg, callback) => {if (props.onBeforeChange) {props.onBeforeChange(prevLoaction, nextLocation, action, callback, unBlock);} else {callback(true); // 没有传递阻塞函数时,需要调用 callback(true) 表示始终不阻塞。}};return (<Router getUserConfirmation={handleConfirm}><GuardHelper onRouteChange={props.onRouteChange} />{props.children}</Router>);
}
- 拦截器
props.history.block
也可以传递一个回调函数,返回值作为阻塞消息。另外,它的位置不影响getUserConfirmation
的生效,只要有就会触发getUserConfirmation
。 - 整个页面的流转,路由信息对象是一致的。所以可以将
location
,action
等信息保存在一个全局变量中。 withRouter
的组件参数,必须是Router
组件的子元素。所以才需要这样特殊处理,定义了一个返回空值的组件,来使用路由参数添加监听和阻塞。
使用:App.jsx
import { Route, Link } from "react-router-dom";
import RouteGuard from "./RouteGuard";
import News from "./News";
import Goods from "./Goods";export default function App() {return (<RouteGuardonRouteChange={(prevLocation, nextLocation, action, unListen) => {console.log(`监听日志:从${prevLocation.pathname}进入页面${nextLocation.pathname},进入方式${action}`);unListen(); // 取消监听,只监听了1次。}}onBeforeChange={(prevLocation, nextLocation, action, callback, unBlock) => {console.log(`阻塞,页面想从${prevLocation.pathname}进入页面${nextLocation.pathname},进入方式${action}`);callback(false); // 阻塞unBlock(); // 取消阻塞,只阻塞了1次。}}><Link to="/">首页</Link><Link to="/news">新闻页</Link><Link to="/goods">商品页</Link><Route path="/news" component={News}></Route><Route path="/goods" component={Goods}></Route></RouteGuard>);
}
3,阻止跳转
有了上面的知识点后,可以实现表单未提交时,阻止跳转。
实现一个 Prompt.jsx,通过属性 when
来控制是否提示。
import { useEffect } from "react";
import { withRouter } from "react-router-dom";function Prompt(props) {useEffect(() => {let unBlcok;if (props.when) {unBlcok = props.history.block(props.message);} else {unBlcok && unBlcok();}return () => {unBlcok && unBlcok();};}, [props.when]);return null;
}export default withRouter(Prompt);
在 App.jsx 中使用,(getUserConfirmation
使用默认值)切换路由时就会提示。
import { BrowserRouter as Router, Route, NavLink } from "react-router-dom";
import Prompt from "./Prompt";function News() {return <div className="page news">News</div>;
}function Goods() {return <div className="page goods">Goods</div>;
}export default function App() {return (<div className="container"><Router><Prompt when={true} message="提示信息"></Prompt><div className="nav-box"><NavLink to="/news">新闻页</NavLink><NavLink to="/goods">商品页</NavLink></div><div className="page-box"><Route path="/news" component={News}></Route><Route path="/goods" component={Goods}></Route></div></Router></div>);
}
表单有信息未提交时,进行提示。
export default function App() {const [value, setValue] = useState("");return (<div className="container"><Router><Prompt when={!value} message="message"></Prompt><inputtype="text"value={value}onChange={(e) => {setValue(e.target.value);}}/><div className="nav-box"><NavLink to="/news">新闻页</NavLink><NavLink to="/goods">商品页</NavLink></div><div className="page-box"><Route path="/news" component={News}></Route><Route path="/goods" component={Goods}></Route></div></Router></div>);
}
而 react-router-dom 已经实现了这个组件,效果和自定义的一致。
import { Prompt } from "react-router-dom";
以上。