最近我在学习 React,用脚手架搭了个管理系统。在动态生成路由这块碰到了点问题,我在网上搜了很多文章资料,有些都是文章跟动态路由都没关系,看的头疼,放个demo,路由全量引入然后加载,这也叫动态生成路由,不太理解。反正得出结论,查了一堆没啥用(当然是我太菜了)。
我理解的动态路由生成是:在用户登录后,通过接口获取到后端返回的当前用户角色的菜单数据结构,然后前端根据这个数据结构动态生成路由,并异步地将其添加到路由配置中。而不是一次性将所有路由都加载到配置文件中。
我得到的答案:说的都是“全量加载”,即前端配置好所有路由,然后根据后端返回的当前角色权限来校验是否能访问。这虽然是一种方法,但我希望通过动态加载的方式,避免每次操作都修改前端配置文件。
React 中没有类似 Vue 的 addRoute 方法,因此我们需要自定义一个方法来动态加载路由。通过在用户登录后,调用接口获取到菜单数据,并根据这个数据动态生成路由,最后通过 window.location.replace 触发路由重新加载。
流程如下:
- 管理员操作菜单配置模块
- 后端保存菜单数据
- 用户登录成功,前端调用接口获取当前角色的菜单数据
- 前端根据菜单数据动态生成路由
初始化的时候是需要让后端添加一个菜单配置模块的,我这里是自己用的node搭的后端。
1. 用户登录和获取菜单数据
login.jsx:用户登录后,通过 API 获取用户信息和菜单数据,然后存储在 Redux 中。
const Login = () => {
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const onSubmit = async (data) => {
const { account, password } = data;
setLoading(true);
// 调用登录接口
const actionResult = await dispatch(loginAsync({ account, password: md5(password) }));
if (actionResult.type === loginAsync.fulfilled.toString()) {
// 获取用户信息
const userResult = await userAPI.getUserInfo();
if (userResult.status === 200) dispatch(setUserInfo(userResult.data));
// 获取菜单数据
const menusResult = await userAPI.getMenu({});
if (menusResult.status === 200) {
await dispatch(setMenus(menusResult.data)); // 存储菜单数据
window.location.replace("/"); // 使用 replace 跳转来触发路由重新加载
}
}
setLoading(false);
};
return <form onSubmit={onSubmit}> {/* 登录表单 */} </form>;
};
2. Redux 存储用户信息和菜单数据
通过 Redux 存储用户信息和菜单数据,并将菜单数据保存在 localStorage 中,以便刷新后继续使用。
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { session, storage } from "../utils/storage";
import { userAPI } from "@/http/api/user";
const initialState = {
token: session.get("token") || "",
userInfo: {},
menus: JSON.parse(storage.get("menus")) || [],
};
const userReducer = createSlice({
name: "user",
initialState,
reducers: {
setUserInfo(state, { payload }) {
state.userInfo = payload;
},
setMenus(state, { payload }) {
state.menus = payload;
storage.set("menus", JSON.stringify(payload)); // 存储菜单到本地
},
loginOut(state) {
state.token = "";
session.removeAll();
storage.removeAll();
window.location.replace("/login");
},
},
});
export const loginAsync = createAsyncThunk("user/login", async (loginData, thunkAPI) => {
const { status, data } = await userAPI.login(loginData);
return status === 200 ? data : thunkAPI.rejectWithValue(data);
});
export const { loginOut, setUserInfo, setMenus } = userReducer.actions;
export default userReducer.reducer;
3. 动态生成路由配置
在路由配置文件 router/index.js 中,根据后端返回的菜单数据生成路由配置,并通过 createBrowserRouter 创建路由。
import { createBrowserRouter, Navigate } from "react-router-dom";
import { lazy } from "react";
import store from "@/store";
// 递归生成路由
const generateRoutes = (menus = []) => {
return menus.map((menu) => {
const { componentPath, children, path } = menu;
const Element = lazy(() => import(`@/views/${componentPath}`)); // 异步加载组件
const route = {
element: (!children || children.length <= 0) && <Element />,
path,
};
if (children && children.length > 0) {
route.children = generateRoutes(children); // 递归生成子路由
}
return route;
});
};
// 获取路由配置
const getRoutes = () => {
const menus = store.getState().user.menus; // 获取菜单数据
if (menus && menus.length > 0) {
const resultRoutes = generateRoutes(menus);
const replaceUrl = resultRoutes[0].children?.[0]?.path || resultRoutes[0].path; // 获取重定向地址到第一个菜单
return [
{
path: "/",
element: <Navigate to={replaceUrl} replace />, // 重定向
},
...resultRoutes,
];
}
return [];
};
// 路由配置
const routes = [
{
path: "/",
element: <Layout />,
children: getRoutes(),
},
{
path: "/login",
element: <Login />,
},
{
path: "*",
element: <NotFound />,
},
];
export default createBrowserRouter(routes);
4. 在 App.js 中渲染路由
最后,在 App.js 中使用 RouterProvider 渲染路由配置。
import { RouterProvider } from "react-router-dom";
import router from "./router";
function App() {
return (
<div className="App">
<RouterProvider router={router} />
</div>
);
}
export default App;
通过以上步骤,成功实现了根据后端数据动态生成路由。具体流程是:用户登录后,前端通过 API 获取当前角色的菜单数据,并动态生成路由配置。这样就能避免每次操作时都需要修改静态的路由配置文件,提升了开发效率和系统灵活性。
版本:
"react": "^18.3.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.22.2",