灏天阁

如何优雅的使用react router v6, 并实现全局守卫

· Yin灏

1.项目初始化

首先用一句话简单总结下 react 是什么?

React 是一个用于构建用户界面的 JavaScript 库

对于项目初始化这话,react 也有自己的脚手架Create React App,react 官网也是建议我们初学者使用 CRA 去初始化一个 react 项目,奈何这个这个脚手架太鸡肋,好多东西都需要开发者手动去配置,另外是基于 webpack 的,所以项目大的时候配置起来是及其麻烦,那怎么办呢?

还好有尤大的 vite,vite 也支持快速生成 react 项目,并内置一些基础配置,使用起来是真香

套用几句vite官网的介绍:

那接下来我们使用 vite 创建一个 react + ts 的项目,我们使用pnpm创建项目,还不会使用 pnpm 的伙伴可以看下如何使用 pnpm 搭建一个 monorepo 工程

pnpm create vite

我们取名为 react-ts,然后选择 react 技术栈,以及 TypeScript 语言

image.png ok,项目初始化完毕后,我们运行如下,标识已经初始化成功!

image.png

2.react-router v6 简单介绍

react router 发布了三个不同的包:

  • react-router:路由核心库,提供许多组件、钩子;
  • react-router-dom: 包括了  react-router  所有内容,同时添加了用于 DOM 的组件,如  <BrowserRouter>
  • react-router-native: 包括了  react-router  所有内容,同时添加了用于 ReactNative 的 API,如<NativeRouter>

与 react-router 5.X 区别:

  • 内置组件的变化:移除  <Switch/>,新增  <Routes/>……
  • 语法变化:component={About}  变成  element={<About/>}……
  • 新增 hook:useParamsuseNavigateuseMatch……
  • 官方明确表示推荐使用函数式组件

3.使用 react route v6 创建路由表

首先我们先安装 react router v6 以及依赖

pnpm install react-router router-router-dom

安装完路由后,我们也想使用 vue 那样,单独创建一个路由表,用来管理路由。

#依次执行下面代码
cd src
mkdir routes
touch index.tsx

我们首先创建两个页面,一个 home 页面,一个 about 页面,然后再 routes/index.tsx 中引入,那是否可以像 vue 那样实现路由懒加载呢,答案是当然可以,react 也为我们光大开发者提供了一个懒加载的 api,就是 React.lazy(),通过这个方法,我们就可以实现路由懒加载

const Home = React.lazy(() => import("../pages/Home"));
const About = React.lazy(() => import("../pages/About"));

interface Route {
  path: string;
  name: string;
  element: ReactNode;
}
export const routes: Route[] = [
  {
    path: "/home",
    name: "home",
    element: <Home />,
  },
  {
    path: "/about",
    name: "about",
    element: <About />,
  },
];

ok 路由表建立后我们如何在页面中优雅的展示呢?

这里不得不提useRoutes这个 hooks, 他能够将我们创建的路由表一一映射成为路由对象

我们也想 vue 开发中一样,在 App.tsx 中引入刚才我们创建好的路由表,

function App() {
  const elements = useRoutes(generateRouter(routes));
  return <div>{elements}</div>;
}

这样一个简单的路由表就创建好了,但是开发中,我们肯定不会是这么简单,比如,切换路由时我们可以增加 loading 转场等,react 也为我们提供了React.Suspense组件,通过 callback props 传入一个 loading,来实现转场效果,我们用该组件将上面的 elements 封装下

<React.Suspense fallback={<Loading />}>{elements}</React.Suspense>

封装完后,在切换路由时,会发现有一个 loading 转场的一个效果。

4.增加全局守卫

如果是一个大型项目,那简单的路由表肯定是不能满足我们的需求的,比如常见的权限校验,前置逻辑等等就无法处理。那 react-router 没有像 vue 一样的类似全局守卫的函数吗?

那还真没有,不得不说 react 是真的优秀,什么都需要自己手动去搞定,搞就搞,谁怕谁,哈哈哈哈

无非我们就是实现一个高阶组件包裹下,然后再高阶组件里面我们去做下路由拦截处理和前置逻辑处理

我们首先对刚才的路由表进行扩展,我们先增加一个权限校验和子组件吧

interface Route {
  path: string,
  name: string,
  element: ReactNode,
  children?: Route[],
  auth?: boolean
}
export const routes: Route[] = [
  {
    path: '/home',
    name: 'home',
    element: <Home />
    auth: true
  },
  {
    path: '/about',
    name: 'about',
    element: <About />
  },
]

紧接着需要对上面的路由表实现一个类似中间件的映射,首先实现一个RouterBeforeEach高阶组件,参数为外部传入的 routes,在该组件内部,将上面我们写的 routes 和传入的做一个对比和判断,如果有 auth 并且为 true,则需要做权限校验,如果没有,则不需要。

export const RouterBeforeEach = ({ children }: any) => {
  const location = useLocation();
  const navigator = useNavigate();
  useEffect(() => {
    let router = getCurrentRouterMap(routes, location.pathname);
    if (!isLogin && router.auth) {
      navigator("/login");
    }
  }, [location.pathname]);
  return children;
};

另外上面的两个 hooks:

  • useLocation: 这个钩子返回当前路由对象。如果您想在当前路由更改时执行一些副作用,我们就可以使用这个 hooks。

  • useNavigate: useNavigate 钩子返回一个函数,这个 hooks 能够让我可以编程式的导航。

上面的 getCurrentRouterMap 就是在路由表中获取到当前的路由对应的路由

const getCurrentRouterMap = (routers: Router[], path: string): Route => {
  for (let router of routers) {
    if (router.path == path) return router;
    if (router.child) {
      const childRouter = getCurrentRouterMap(router.child, path);
      if (childRouter) return childRouter;
    }
  }
  return routes[routes.length - 1];
};

然后,我们在去 App.tsx 中用 RouterBeforeEach 包裹下 elements

 <React.Suspense fallback={<Loading/>}>
    <RouterBeforeEach>
      {elements}
    </RouterBeforeEach>
  </React.Suspense>
</div>

ok,到这里用 react-router 6 实现一个路由表,并实现一个全局守卫已经完成。

5.总结

总的来说,react 对初学者确实比 vue 要要求高些,很多东西需要自己手动去封装和处理,不像 vue,在框架层面已经帮我们做了太多的处理,对开发者还是非常友好的;但是不能说 react 就不友好,只能说各有各的好处,相信坚持下去他会给我们能力上带来很大的提升。

- Book Lists -