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
中记录和更新from
、to
的值,可以根据自己的需要选择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;
}
结合前面的钩子,就可以比较是否为同一页面反复跳转了。