基于第一步,来对路由系统进行初步改造:juejin.cn/post/759366... 若有需要,请点击跳转 ~
实现思路
- 基于 react-router-dom v7
- 利用 webpack 注入的require 变量,在编译时动态扫描目录,获取匹配的文件列表.
- 遍历文件目录,使用 lazy 动态导入组件
- 返回 routes
事情开始变得不简单喽 ~
补充知识
什么是 require.context?
Webpack 的 API,用于在编译时动态扫描目录,获取匹配的文件列表。 语法
js
require.context(
directory, // 扫描的目录(相对路径)
useSubdirectories, // 是否递归扫描子目录
regExp // 匹配文件的正则表达式
)
示例
js
// 扫描 ../pages 目录下的所有 index.tsx 文件
const context = require.context('../pages', true, /index\.tsx$/);
// 获取所有匹配的文件路径
context.keys();
// ["./chat/index.tsx", "./file/index.tsx", "./shiti/index.tsx"]
// 动态导入某个文件
context('./chat/index.tsx');
require 是哪里来的?
Webpack 在编译时会注入一些全局变量:
| 标题 | 来源 | 说明 |
|---|---|---|
| require | Webpack | 模块导入函数(CommonJS 风格) |
| require.context | Webpack | Webpack 特有的 API |
| import.meta.glob | Vite | Vite 等价功能 |
| __dirname | Node.js | 当前文件所在目录 |
lazy
lazy 的作用
实现代码分割(Code Splitting),把不同页面的代码拆分成独立的 bundle,按需加载:
js
不使用 lazy:
├── main.js (2MB) ← 所有页面代码打包在一起,首次加载很慢
使用 lazy:
├── main.js (200KB) ← 首屏只加载核心代码
├── chat.js (150KB) ← 访问 /chat 时才加载
├── shiti.js (100KB) ← 访问 /shiti 时才加载
└── upload.js (80KB) ← 访问 /upload 时才加载
不是必须。可以不用,用了更好 ~
好啦,知识铺垫结束 ~ ,正文开始~
文件大迁移 ~
首先,一个约定式路由系统,需要先约定 文件目录, 这里我选 src/pages 目录,然后先对目录做一个整理的大迁移 --- 将原来 src 目录下的页面文件统统移到 src/pages 目录下
像这样

扫描 pages 目录生成文件配置
src/utils/routes.tsx
js
import { ComponentType, lazy } from "react"
export interface RouteItem { path: string, component: ComponentType }
export const RouteWrapper = ({
component: Component
}: { component: React.ElementType }) => {
return <Component />; }
// 路由生成器
export const generateRoutes = () => {
const routes: RouteItem[] = [
{ path: '/chat', component: lazy(() => import('../pages/chat')) },
{ path: '/shiti', component: lazy(() => import('../pages/shiti')) },
{ path: '/file', component: lazy(() => import('../pages/file')) }
]
return routes;
}
替换pages/index.tsx 中的写法
额,好像有点长,不管了,先这样,下一步再优化吧 ~
js
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import { Suspense } from 'react';
import './index.css';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Layout from './layouts'
import { generateRoutes, RouteWrapper } from "./utils/routes";
const App = () => {
const routes = generateRoutes()
return (
<ConfigProvider locale={zhCN}>
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />} >
{
routes.map(item => {
const { path, component } = item || {}
return (
<Route
key={path}
path={path}
element={
<Suspense fallback={<>loading...</>}>
<RouteWrapper component={component} />
</Suspense>
}
/>
)
})
}
</Route>
</Routes>
</BrowserRouter>
</ConfigProvider>
)
}
export default App
加入 webpack 变量,编译时扫描文件目录
上面 generateRoutes 的自动化 ~
ts
// 扫描 pages 目录生成路由配置
import { ComponentType, lazy } from "react"
export interface RouteItem {
path: string,
component: ComponentType
}
// 由于 React 不支持小写字母开头的组件名称,这里我们需要用 wrapper 包裹一下
export const RouteWrapper = ({
component: Component
}: { component: React.ElementType }) => {
return <Component />;
}
/**
* 路由生成器
* 约定规则:
*
* - src/pages/chat/index.tsx → /chat
* - src/pages/file/index.tsx → /file
* - src/pages/shiti/index.tsx → /shiti
*/
export const generateRoutes = (): RouteItem[] => {
const pagesContext = require.context('../pages', true, /index\.tsx$/);
// 遍历所有匹配的文件
// 注意: 这里没有体现递归遍历,不过这个不重要,项目目前不需要
pagesContext.keys().forEach((filePath: string) => {
// filePath 示例: "./chat/index.tsx"
const pathParts = filePath.split('/');
const length = pathParts.length;
const lastSplitIndex = filePath.lastIndexOf('/'); // 找到最后一个 / 的位置
const path = filePath.slice(2, lastSplitIndex); // 去掉 ./
// 生成路径
const routePath = `/${path}`;
// 使用lazy 动态导入组件
routes.push({
path: routePath,
component: lazy(() => import (`../pages/${path}`))
})
// 添加首页,默认跳转到第一个页面
if (routes.length > 0) {
routes.unshift({
path: '/',
component: routes[0].component
})
}
})
return routes;
}
OK 今天就到这啦 ~ 明天见,本专栏正在持续更新哦 ~
下一步:配置式路由和约定式路由共存 ~ 敬请期待 ~