什么是服务端路由?
平时我们项目的路由配置可能是这样的:
export const router: RoutersConfig[] = [
{
path: '/',
name: 'app',
element: <BasicLayout />,
children: [
{
path: '/',
name: 'dashboard',
element: <Dashboard />,
index: true
}, {
path: 'login2',
name: 'login2',
element: <Login/>,
free: true,
children: [
{
path: 'async2',
name: 'async2',
element: <Login />,
free: true
}
]
}, {
path: '/async',
name: 'async',
element: <Login />,
free: true
}
]
}, {
path: '/login',
name: 'login',
element: <Login />,
}
]
export function dispatchRouter(config: RoutersConfig[], parentPath = '') {
return config.map(item => {
let props = {
...item,
children: null,
};
if (parentPath) {
props.path = `${parentPath}${item.path}`;
}
return <Route {...props} key={props.path}>
{ item.children && dispatchRouter(item.children, props.path) }
</Route>
})
}
将所有的路由单独放到一个文件里。这也是最方便的一种方式。然后通过dispatchRouter
递归route tree。最终生成这样的结构:
如果我们要求路由根据用户的权限去生成,这种方式就不能满足。那么我们就需要服务端路由。简单的说就是:route tree不再是前端定义, 而是根据后端返回的数据去生成。
但是有一个问题就是,后端和我交互的数据一般都是json格式,组件是不能被后端返回的,除非使用SSR。关于SSR的内容暂时不做赘述。
所以只能让后端返回组件路径,我们动态的导入从而生成各个<Route />
。所以服务端路由主要就问题就是解决动态导入的问题。下面通过代码一步一步的解决这个问题。
Coding time
- 我们首先搭建一个react项目,不限
vite
或者webpack
, 这里我使用的是vite
- 并安装上
react-router-dom
或者react-router
- 开始撸代码
项目结构设计
首先我们确定有两个通用路由,分别是登录路由和root路由。所以这两个路由我们仍然在前端提前定义好
<BrowserRouter>
<Routes>
<Route path="/" element={<SecurityLayout />}>
<Route path="*" element={<div>405</div>} />
</Route>
<Route path="/login" element={<Login />}>
<Route path="/login/register" element={<div>注册</div>} />
</Route>
<Route path="*" element={<div>404</div>} />
</Routes>
</BrowserRouter>
从上面代码中可以看出我们定义了一个404路由,一个根路由和一个登录路由。
我们需要在请求路由的时候做个等待效果
const App = () => {
const [loading, setLoading] = useState(true);
if (loading) {
return <div>加载中....</div>;
}
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<SecurityLayout />}>
<Route path="*" element={<div>405</div>} />
</Route>
<Route path="/login" element={<Login />}>
<Route path="/login/register" element={<div>注册</div>} />
</Route>
<Route path="*" element={<div>404</div>} />
</Routes>
</BrowserRouter>
);
};
效果就出来了
请求路由数据
我们用一个timeout来模拟api请求
const mockRoutes = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
path: '/',
name: 'dashboard',
component: '/dashboard/index',
index: true,
},
{
path: '/requestList',
name: 'dashboard1',
component: '/dashboard/index',
// index: true
},
{
path: '/userList',
name: 'userList',
component: '/userList/index',
}
]);
}, 3000);
});
};
然后在useEffect
内请求数据
const fetechRoutes = async () => {
const serverSideRoutes = (await mockRoutes()) as any[];
};
useEffect(() => {
fetechRoutes();
}, []);
递归生成路由并保持到state内
const modules = import.meta.glob('./pages/*/*.tsx');
const pollRoutes = (items: any[], parentPath = '') => {
return items.map((item) => {
const props = {
...item,
children: null
};
if (item.component) {
// 动态导入组件
const Component = modules[`./pages${item.component}.tsx`];
const MyCom = lazy(Component as any);
props.element = <MyCom />;
}
if (parentPath) {
props.path = `${parentPath}${item.path}`;
}
return (
// eslint-disable-next-line react/jsx-props-no-spreading
<Route {...props} key={props.path}>
{item.children && dispatchRouter(item.children, props.path)}
</Route>
);
});
};
const fetechRoutes = async () => {
const serverSideRoutes = (await mockRoutes()) as any[];
const routerTree = pollRoutes(serverSideRoutes);
setRoutes(routerTree);
setLoading(false);
};
useEffect(() => {
fetechRoutes();
}, []);
vite不支持
import(
$url)
的这种写法, 但是提供import.meta.glob
所以如果项目使用的是vite就不能直接用import
如果是webpack 只需要以下操作即可
if (item.component) {
// 动态导入组件
const Component = modules[`./pages${item.component}.tsx`];
const MyCom = lazy(Component as any);
props.element = <MyCom />;
}
/* 改成 */
if (item.component) {
// 动态导入组件
const MyCom = lazy(() => import(`${url}`));
props.element = MyCom;
}