地球上没有对手的SSR框架,这是一个非常优秀的框架,核心代码量很少,相较于 Next.js 几十万行代码而言,学习难度要小很多。所以对 ssr 感兴趣的小伙伴,可以从这个框架开始你的 ssr之旅~
今天我们先来讲讲约定式路由
什么是约定式路由
约定式路由即根据前端文件夹结构来自动的生成前端路由配置,无需手动配置路由信息的方法,通常需要约定好项目文件的结构,开发者按照约定来创建文件。这种方法使得开发者无需手动编写大量的路由配置,简化了路由管理的复杂性。
举个例子,现在有如下文件夹结构,会生成怎样的路由呢?
pages
├─ detail
│ ├─ fetch.ts
│ └─ render$id.tsx
└─ index
├─ fetch.ts
└─ render.tsx
我们约定 pages 文件夹下存放的每一个文件代表一个页面,同时,我们约定 render 文件代表渲染这个页面的组件。
一个页面的路由有多种形式:
- 普通路由即
/
,/detail
这种。对于/index/render.tsx
,最终就会映射为/
,index
不会保留。 - 动态路由即
/detail/:id
这种后面是动态的参数的形式的路由。/detail/render$id.tsx
会映射为/detail/:id
,如果是/detail/render$id$bar.tsx
会映射为/detail/:id/:bar
。 - 可选参数路由即 url 中带有 query 参数形式,比如
/detail?a=b
,由于?
符号无法作为文件名使用,所以该框架使用了#
代替。/detail/render$id#.tsx
会映射为/detail/:id?
。 - 多级路由即
/user/detail
这种,对于/user/detail/render.tsx
就会映射为/user/detail
。
约定式路由的实现
接下来我们讲一下约定式路由的实现,代码量很少,核心就是一个回溯算法!
下面给出源码实现,删减了一些目前无需关注的代码,连 50 行都不到!
const renderRoutes = async (pageDir, pathRecord, route) => {
let arr = []
const pagesFolder = await fs.readdir(pageDir)
const prefixPath = pathRecord.join('/')
const aliasPath = `@/pages${prefixPath}`
const routeArr = []
for (const pageFiles of pagesFolders) {
const abFolder = path.join(pageDir, pageFiles)
const isDirectory = (await fs.stat(abFolder)).isDirectory()
if (isDirectory) {
// 如果是文件夹则递归下去, 记录路径
pathRecord.push(pageFiles)
const childArr = await renderRoutes(abFolder, pathRecord, Object.assign({}, route))
pathRecord.pop() // 回溯
arr = arr.concat(childArr)
} else {
// 遍历一个文件夹下面的所有文件
if (!pageFiles.includes('render')) {
continue
}
// 拿到具体的文件
if (pageFiles.includes('render$')) {
// 非普通路由
/* /news/:id */
route.path = `${prefixPath}/:${getDynamicParam(pageFiles)}`
route.component = `${aliasPath}/${pageFiles}`
} else if (pageFiles.includes('render')) {
// 普通路由
/* /news */
route.path = `${prefixPath}`
route.component = `${aliasPath}/${pageFiles}`
}
}
routeArr.forEach((r) => {
if (r.path?.includes('index')) {
// /index 映射为 /
if (r.path.split('/').length >= 3) {
r.path = r.path.replace('/index', '')
} else {
r.path = r.path.replace('index', '')
}
}
r.path && arr.push(r)
})
return arr
}
-
函数接收三个参数,pageDir 代表所有页面的目录路径,首次传入是 pages 目录所在的路径;pathRecord是一个数组用来记录页面路径;route 是存储页面路由的对象
-
首先读取
pageDir
目录下的所有文件和文件夹,然后根据传入的pathRecord
数组拼接出prefixPath
(前缀路径)和aliasPath
(别名路径) -
遍历
pageDir
目录下的所有文件和文件夹- 如果是文件夹,则递归调用
renderRoutes
函数,同时更新pathRecord
- 如果是文件,并且不包含 render 命名的文件,则跳过,表示没有要渲染的页面。如果包含,则去查看是否包含
render$
开头命名的文件,如果有则会调用getDynamicParam
方法对文件名进行一个解析,解析出动态路由、可选参数路由、通配符路由;如果没有以render$
开头命名的文件,则判断是否有以 render 开头的文件,即普通路由。
- 如果是文件夹,则递归调用
-
最后其实也是对路由做了一点转化,如果 pages 下有以 index 命名的文件夹,会转成空字符串。举个例子,我们页面统一的前缀是
/srr
那么/pages/index/render.tsx
最后会被映射成的路由就是/ssr
,而/pages/detail/render.tsx
会被映射成路由/ssr/detail
OK,解析约定式路由的核心代码就这么少,其实就是一个递归文件夹的回溯。
总结
本文讲解了约定式路由的实现,在 Next.js、Umi 等框架中,都有约定式路由的身影,只要我们知道了框架中它是如何约定路由的,按照它的约定去创建文件结构,能够极大简化我们管理路由的负担。
同时,从约定式路由这一点就能看出,该框架的实现就是极简的,也就 2000+ 的代码就实现了一个可在实际项目中使用的 ssr 框架,绝不是很多文章写的 demo 实现。
原文链接:https://juejin.cn/post/7349087888048586787 作者:ShowBuger