react 您所在的位置:网站首页 react路由匹配规则 react

react

2023-12-19 03:07| 来源: 网络整理| 查看: 265

前言

react-router 更新到v6版本应该有好一段时间了,但是v6自己也没真正去实践过,用过v5版本的都知道如果配置路由守卫、拦截等或者像vue那样的路由数组的话是很麻烦的,还要用到react-router-config的这个库,但是升级到v6版本后api改动就变得很大了,比如说可以直接支持路由数组的配置,history跳转改成用useNavigate、switch改成用Routes等,刚好最近也是想基于最新的react和react-router来搭建一个博客网站,特别是在配权限路由的时候花费了不少时间(可能是我比较菜的原因 😬),整体框架是基于vite搭建的,数据存储用的是redux-toolkit,请求用的是redux-toolkit内置的query模块(功能还是蛮强大的),下面讲一下我是怎么一步步配置的(有什么错误欢迎大家指出来哈)

路由配置 useRouters 的使用

useRoutes 是 React Router v6 中的一个 Hook,它的作用是动态地配置路由,它接收一个路由数组,并使用匹配到的路由来渲染相应的组件。

与传统的配置路由的方式不同,useRoutes 的优势在于它可以动态地配置路由,使得在应用的生命周期中更改路由变得更加容易。

我们在router的文件夹里新建了一个index.tsx(存放我们的路由数组)和AuthRouter(路由权限控制。。。)的文件

image.png

index.tsx

import { Suspense, lazy, ReactNode } from "react"; import { Outlet, Navigate, useRoutes, Route } from "react-router-dom"; import Layout from "../components/Layout"; import LoginPage from "../login"; import NotFound from "../components/404"; const Home = lazy(() => import("../home")); const About = lazy(() => import("../about")); const LinkPage = lazy(() => import("../link")); const MdPage = lazy(() => import("../md")); // const Layout = lazy(() => import("../components/Layout")); const LayoutComponent = ({ children }: any) => { return ( ); }; export interface RouteConfig { path: string; element: React.ReactNode; auth: boolean; children?: RouteConfig[]; redirect?:string } export const routers = [ { path: "/login", element: , auth: false }, { path: "/", element: , auth: true, children: [ { path: "/home", element: , auth: true }, { path: "/about", element: , auth: true }, { path: "/auth", element: , auth: true }, { path: "/link", element: , auth: true }, { path: "/md/:id", element: , auth: true }, { path: "*", element: , auth: true }, ], }, ];

主要是看我们的AuthRouter

import { message } from "antd"; import { useEffect } from "react"; import { useSelector } from "react-redux"; import { matchRoutes, useLocation, useNavigate } from "react-router-dom"; import { routers } from "./index"; const AuthRoute = ({ children, auth }: any) => { const navigate = useNavigate(); const token = localStorage.getItem("blogToken") || ""; const loginState = useSelector((state: any) => state.public.loginState); const mathchs = matchRoutes(routers, location); const isExist = mathchs?.some((item) => item.pathname == location.pathname); useEffect(() => { if (token == "" && auth) { message.error("token 过期,请重新登录!"); navigate("/login"); } // 这里判断条件是:token 存在并且是匹配到路由并且是已经登录的状态 if (token && isExist && loginState == "login") { // 如果你已经登录了,但是你通过浏览器里直接访问login的话不允许直接跳转到login路由,必须通过logout来控制退出登录或者是token过期返回登录界面 if (location.pathname == "/" || location.pathname == "/login") { navigate("/home"); } else { // 如果是其他路由就跳到其他的路由 navigate(location.pathname); } } }, [token, location.pathname]); return children; }; export default AuthRoute;

然后在我们的App.tsx组件里专门写了个方法处理渲染路由

import { ReactNode, useCallback, useEffect, useState } from "react"; import { useSelector } from "react-redux"; import { Route, Routes, } from "react-router-dom"; import { RouteConfig, routers } from "./router"; import AuthRoute from "./router/AuthRoute"; const App = () => { const loginState = useSelector((state: any) => state.public.loginState); // 处理我们的routers const RouteAuthFun = ( (routeList: RouteConfig[]) => { return routeList.map( (item: { path: string; auth: boolean; element: ReactNode; children?: any; }) => { return ( {/* 递归调用,因为可能存在多级的路由 */} {item?.children && RouteAuthFun(item.children)} ); } ); } ); return {RouteAuthFun(routers)}; }; export default App;

这里要注意一点的是必须要用Routes来包裹我们Route组件,这里是react-router v6强制要求的,不包裹的话就会报错

这里的router就大概配置到这里,如果要配置一些更复杂的路由权限的话也可以在这个基础上来配置,比如说我这里配置是通过auth是true或者false来判断,你也可以将auth写成数组的模式,通过里面的数组权限来过滤掉路由来访问不同的路由就可以了,比如说auth=["user","admin"],然后你根据auth的user访问的是user的路由,写个过滤的方法将routes数组过滤掉就行了,大概的思路就是这样

redux-toolkit 和 query配置

redux-toolkit相比于传统的redux主要的作用就是简化redux代码冗长、简化操作等功能,它是将一些列功能集合在一个切片中,更好的方便我们去管理我们的状态数据,而不用像传统的redux又要分aciton、reducer等文件管理。

redux-toolkit 中的 query 功能是一种从 Redux store 中查询数据的方式。它使用记忆化查询来提高性能,并可以通过简单的语法从 store 中读取数据。这对于组件在不执行重新渲染的情况下获取数据,并在 store 中的数据发生更改时重新渲染,非常有用。

下面我以loginApi.ts为例,里面存放着两个请求获取验证码和登录的请求:

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react"; import { GET_CAPTCHA, LOGIN } from "../api/api"; import { baseUrl, InterceptorsResponse } from "../api/baseQuery"; import { message } from "antd"; // 登录接口 const loginApi = createApi({ reducerPath: "loginApi", // Api的标识,不能和其他的Api或reducer重复 baseQuery: fetchBaseQuery({ baseUrl, }), endpoints(build) { return { // 登录 login: build.mutation({ query(args) { return { url: LOGIN, params: args, }; }, }), // 获取验证码 getCaptcha: build.query({ query() { return GET_CAPTCHA; }, // transformResponse 用来转换响应数据的格式 transformResponse(res: any) { // console.log(res, "用来转换响应数据的格式...."); return InterceptorsResponse(res); }, // keepUnusedDataFor: 60, // 设置数据缓存的时间,单位秒 默认60s }), }; }, // endpoints 用来指定Api中的各种功能,是一个方法,需要一个对象作为返回值 }); // Api对象创建后,对象中会根据各种方法自动的生成对应的钩子函数 // 通过这些钩子函数,可以来向服务器发送请求 // 钩子函数的命名规则 fetchLogin --> useFetchLoginQuery export const { useLoginMutation, useGetCaptchaQuery } = loginApi; export default loginApi;

里面的fetchBaseQuery和InterceptorsResponse这里我统一封装了一个函数去处理一个事请求前在每个api中的header加token验证和响应后拦截的处理,具体看下面的baseQuery的代码 baseQuery.ts

import { fetchBaseQuery } from "@reduxjs/toolkit/dist/query"; import { message } from "antd"; export const baseUrl = "http://localhost:8080"; type ResponseData = { code: number; data: any; message: string; }; export const baseQuery = fetchBaseQuery({ baseUrl, timeout: 1000, // 对所有的请求进行预处理 prepareHeaders: (headers) => { const token = localStorage.getItem("blogToken"); if (token) { headers.set("Authorization", `Bearer ${token}`); } return headers; }, }); // 统一响应拦截器 export const InterceptorsResponse = (res: any) => { try { const msg = res.message; switch (res.code) { case 200: return res; case 401: message.error(msg); localStorage.clear(); window.location.href = "/login"; return Promise.reject("error"); case 500: message.error(msg); return Promise.reject(new Error(msg)); default: message.error(msg); return Promise.reject("error"); } } catch (error: any) { let { message } = error; if (message == "Network Error") { message = "后端接口连接异常"; } else if (message.includes("timeout")) { message = "系统接口请求超时"; } else if (message.includes("Request failed with status code")) { message = "系统接口" + message.substr(message.length - 3) + "异常"; } return Promise.reject(error); } };

然后看我们的store.ts最终配置

import { combineReducers, configureStore } from "@reduxjs/toolkit"; import { persistStore, persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; import reducer from "./reducer"; import mdApi from "./mdApi"; import loginApi from "./loginApi"; const persistConfig = { key: "root", storage, // 配置持久化存在白名单 whitelist: ["public"], }; const sliceReducer = combineReducers({ [mdApi.reducerPath]: mdApi.reducer, [loginApi.reducerPath]: loginApi.reducer, ...reducer, }); const persistedReducer = persistReducer(persistConfig, sliceReducer); const store = configureStore({ reducer: persistedReducer, // middleware: (middle) => middle().concat([ loginApi.middleware, homeApi.middleware ]) middleware: (middle) => middle().concat([mdApi.middleware, loginApi.middleware]), }); const persistor = persistStore(store); export { store, persistor };

这里要注意的是:如果你是有用到query模块配置的话,必须要在middleware中间件配置这里配置上你的配置,如果是普通的reducer是不需要配置到这里的

我们在login登录界面调用我们loginApi.ts时这样调用

import React, { useEffect, useState } from "react"; import { Form, Input, Button, message, Checkbox } from "antd"; import { UserOutlined, LockOutlined, EyeInvisibleOutlined, EyeTwoTone, } from "@ant-design/icons"; import { useNavigate } from "react-router-dom"; import styles from "./login.module.css"; import "./square.css"; import { useGetCaptchaQuery, useLoginMutation } from "../store/loginApi"; import { setLogin, setUserInfo } from "../store/reducer/pubilcSlice"; import { useDispatch } from "react-redux"; const LoginPage: React.FC = () => { const navigate = useNavigate(); const dispatch = useDispatch(); const { // 数据 data: captcha, // 刷新方法 refetch: refetchCaptcha, // 判断是否出错 isError, } = useGetCaptchaQuery(null, { // 响应前对数据进行处理 selectFromResult: (res) => { const newRes = { ...res }; if (newRes.data?.code == 200) { newRes.data = { ...newRes.data, captcha_img: `data:image/png;base64,${newRes?.data?.data?.captcha_img}`, }; } return newRes; }, }); const [fetchLogin, { isLoading }] = useLoginMutation(); useEffect(() => { if (isError) { message.error("后端接口连接异常!"); } }, [isError]); // const handleVerfiCaptcha = (rule: any, value: string, callback: any) => { // if ( // value && // value.toLocaleUpperCase() !== captcha?.captcha_id.toLocaleUpperCase() // ) { // callback(new Error("Verification code error")); // } else { // callback(); // } // }; const onFinish = async (values: any) => { try { const res: any = await fetchLogin(values).unwrap(); if (res?.code == 200) { message.success(res?.message); const token = res?.data?.token; localStorage.setItem("blogToken", token); dispatch(setLogin("login")); dispatch(setUserInfo(res?.data?.user)); navigate("/home"); } else { message.error(res?.message); // 重新刷新验证码 setTimeout(() => { refetchCaptcha(); }, 1000); } } catch (error) { console.log(error); } }; const onRefreshCatch = () => { refetchCaptcha(); }; return ( Blog管理系统 visible ? : } /> {/* */} 记住我 忘记密码 Login ); }; export default LoginPage; 最后

我们最后的示例就到此结束(还没完善的),这个案例后面也会继续慢慢完善的,希望最后也能帮到大家,也欢迎大家能提出issue,感谢🙏,也麻烦大家给个star

完整案例请看这里:demo

ui:

image.png

image.png

这里还有我自己用Go搭建的后端框架,和这个前端是搭配一起的,后面的也会慢慢开源起来,敬请期待!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有