likes
comments
collection
share

React 实现路由监听(from, to)

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

React 实现路由监听(from, to)

文章首发于个人博客:React 实现路由监听(from, to) -Aiysosis Blog(aiysosis.ink)

前言

在使用Vue的路由时,路由守卫是一个非常方便的功能,开发者可以很方便地在其中进行一些拦截、鉴权相关操作。因此在使用React构建应用时,我自然而然的去寻找类似的解决方案。但经过一顿搜索,结果都不尽如人意。那就只好自己封装一个了。

依赖

  • react-router-dom v6
  • react v18

实现

监听的核心原理基于useEffect,和useLocation,通过useEffect监听当前location的变化,这样就实现的最基本的监听结构:

const location = useLocation();
useEffect(() => {
    //记录路径
}, [location]);

然后,我们可以在useEffect中记录和更新fromto的值,可以根据自己的需要选择from、to的数据类型,这里我使用了React-router提供的Location类型。

更新逻辑为:将to的值赋给from,然后将新的location赋值给to

import { Location, useLocation } from "react-router-dom";
​
type LocationTrans = {
    from: Location;
    to: Location;
};
​
const location = useLocation();
const locationState = useRef<LocationTrans>({
    from: null,
    to: null,
});
useEffect(() => {
    locationState.current.from = locationState.current.to;
    locationState.current.to = location;
}, [location]);

::: warning

locationState需要用useRef包装一层,以保证Context.Consumer访问的是同一对象。

:::

最后,利用React的Context进行封装,将其封装成一个组件和一个hook,使用者可以通过这个组件来进行监听,通过hook快速访问数据。我将这些代码放在了同一个.tsx文件中,保证了逻辑的高内聚。

import React, { createContext, useContext, useEffect, useRef } from "react";
import { Location, useLocation } from "react-router-dom";
​
type LocationTrans = {
    from: Location;
    to: Location;
};
​
export const LocationContext =
    createContext<React.MutableRefObject<LocationTrans>>(null);
​
export function WithLocationListener(props: { children: React.ReactNode }) {
    const location = useLocation();
​
    const locationState = useRef<LocationTrans>({
        from: null,
        to: null,
    });
​
    useEffect(() => {
        locationState.current.from = locationState.current.to;
        locationState.current.to = location;
    }, [location]);
​
    return (
        <LocationContext.Provider value={locationState}>
            {props.children}
        </LocationContext.Provider>
    );
}
​
export function useLocationConsumer(): LocationTrans {
    const ref = useContext(LocationContext);
    return ref.current;
}

具体使用

:::warning

这个组件只能在RouterProvider的子组件中使用,因为useLocation只能在这个范围内使用。

:::

以本人的项目为例,我的路由使用了Layout组件,因此我选择将监听放在这个组件中:

src/layout/index.tsx

//import ....
function Layout() {
    //....
    return (
        <WithLocationListener>
            {//...}
        </WithLocationListener>
    );
}

在需要用到路由信息的页面:

const { from, to } = useLocationConsumer();

杂项

在编写的过程中,我还遇到了比较页面是否为同一个的需求,查阅文档后,我发现RouteObject中有一个id字段可以符合我的需求,所以顺便封装了一个根据location获取对应route的函数,原理很简单:使用react-router的底层函数matchRoutes,得到一个匹配路由数组,最后一项的route属性就是答案。

import { Location, matchRoutes } from "react-router-dom";
import { routes } from ".";
​
export function getRouteObjectByLocation(location: Location) {
    const matched = matchRoutes(routes, location);
    const n = matched.length;
    if (n > 0) return matched[n - 1].route;
    else return null;
}

结合前面的钩子,就可以比较是否为同一页面反复跳转了。

转载自:https://juejin.cn/post/7195910055497580600
评论
请登录