React@16.x(41)路由v5.x(6)常见应用场景(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>
            <RouteGuard
                onRouteChange={(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 的默认值
<Router
   getUserConfirmation={(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 得到阻塞消息,再调用 getUserConfirmationcallback 来决定是否阻塞。

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>
    );
}
  1. 拦截器 props.history.block 也可以传递一个回调函数,返回值作为阻塞消息。另外,它的位置不影响 getUserConfirmation 的生效,只要有就会触发getUserConfirmation
  2. 整个页面的流转,路由信息对象是一致的。所以可以将 locationaction 等信息保存在一个全局变量中。
  3. 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 (
        <RouteGuard
            onRouteChange={(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>
                <input
                    type="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";

以上。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下雪天的夏风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值