如何设计并实现一个所谓"全网最快的路由数据加载 loader"的思路 ------ vue/react
前言
这个概念来自于框架 remix ,所谓全网最快有些夸张,很多国内外的大框架也都是这么喊的,所以我也就这么说了~
理论上来说确实快,实现起来对于常见大多数情况来说其实并不难,应该说是比较麻烦和繁琐而已
本文会从,本着结果相同,过程随意的角度 阐述如何制作生产可用的版本,以及如何和框架集成的思路,由于最终效果需要根据实际封装而定,所以理念和设计会优先大于教学
解决的什么问题
英文好的可以看官网原文,以下是我用些大白话的解释
这东西是用于解决单页面应用,在切换路由时,导致的接口加载瀑布流的问题诞生的
什么是瀑布流呢
瀑布流是当进入页面,或者切换路由时,用到的所有组件的接口请求顺序。组件是存在上下级的,加载顺序一定是由爷爷到孙子顺序加载,上边的不加载完,底下的不出来。进而导致接口请求的顺序会像瀑布一样

而 loader
要做的就是,在刚进页面/路由切换时,跳过路由校验,第一时间把所有要展示的,与路由树绑定的 loader 函数给执行下。把数据收集起来,当组件被展示在页面上时,数据可能已经好了
js
//路由表伪代码
const routes = [
{
path: "/", loader: homeLoader,
children: [
{ path: "about", loader: aboutLoader },
{ path: "login", loader: loginLoader },
]
},
{ path: "xxx", loader: xxxLoader }
]
比如这个例子,如果进入了 /about
页面,那么就会命中 homeLoader, aboutLoader
,它们两个会被立即执行,其他的则不会管
loader
通常存在于路由表中,从实际使用角度上来说,完全可以认为它就是用来返回数据的函数,主要是放请求接口的函数,比如 async function loader(){ return {msg: "loader"} }
这个简单的函数就可以说它是个 loader
函数
loader
函数需要结合单页面的组件路由来使用,比如 vue-router react-router-dom
等等
在组件中使用时需要通过一个 hooks
来接收,这东西是封装 loader
的人提供的
整个流程,大致如下
- 进入页面
- 收集所有路由相关的
loader
函数给运行 - 将执行结果全部收集起来,放哪看作者心情
- 对外提供访问这些数据的手段
- 组件内使用该方式获取数据
实现上要考虑的问题
过程第一眼看上去并不复杂,甚至觉得好像没什么难的,但在如何处理数据上会有坑
- 数据竟态
路由切换时,不管旧的有没结果都要取消或者丢弃
这对于 react
挺坑的,如果涉及到权限,路由封装等其他需求,由于相关逻辑一定是放在路由拦截的地方里,所以必定涉及重复进入的问题,那么就要好好想想了
想想怎么保证多次进入时,多次离开时,不会反复执行loader
,想想在多次进入时的前几次,怎么不会把 loader
的结果误删了
- 缓存
如果想把路由守卫封装的强大点,那么可能不同的路由会有不同的细致策略,也就是可能会发生路由守卫嵌套的情况,这个时候是必须要做缓存的,因为祖先守卫已经把loader
的逻辑执行过了,所以此时的逻辑必定是复用
正常判断是用的路由的useLocation().pathname
来做唯一判断,可既然涉及到了缓存,那么就得认真考虑这个复用的值是不是旧的
- 对组件内的
hooks
要提供的数据必须得是最新的 - SSR 服务端渲染怎么支持
- react 在路由切换时是从父组件开始的,请问怎么拿到屁股后哪些才是要用到的子组件呢,如果不知道就拿不到子组件的 loader
- ....
这是几个比较典型的,必须要考虑要解决的问题,react
是重灾区,因为人家什么都不提供,路由守卫,拦截,懒加载,...都得自己来,然后在这个基础上把loader
的逻辑加上去,感觉没什么问题的代码,刚开始跑起来,要么重复跑、要么某些数据是旧的、要么死循环...,总之意外的问题一堆
你问我Vue
?这些问题很多就不存在,难度大幅度降低
如何做设计,如何实现的思路
设计上其实是很灵活的,我先把通用的实现思路列出来(兼容SSR),这里列出,一个简单的,一个复杂的
简单的(client)
- 正常的写路由表,要静态,一定要是平铺的!!!,像一般后端管理框架搞的动态的路由后边处理起来会折磨死人
- 确保在写的时候,同级的静态路由要放在动态的前边,因为这两个
/动态路由
和/abc
路由比,一旦前者放前,后者在后续的可能处理中处理不了 - 做出两个如下的结构,可以递归第一步的路由表做出来
js
//比如可以
const routes = [
{
path: "/",
fullPath: "/",
},
{
path: "about",
fullPath: "/about"
}
]
- 剩下的就是使用取值之类的,这个通常是放在组件的上下文中,然后内部通过上下文取。按照喜欢的方式自由发挥即可
复杂的(client + SSR)
-
正常的写路由表,要静态,像一般后端管理框架搞的动态的路由后边处理起来会折磨死人
-
对列表进行排序,因为这两个
/动态路由
和/abc
路由比,一旦前者放前,后者可能会进不去 -
数据结构要支持,要做出以下两个,这里需要递归第一步的路由表制作 -- demo看下边
- 一个树形的,每个路由都有属于自己的全路径的,存在父子引用字段的路由表
- 一个把树形列表扁平化的列表
-
进入路由时
- 客户端扫描出所有用到的
loader
执行 - 服务端从
routeMap
扫出来进的是哪个
- 客户端扫描出所有用到的
-
剩下的就是使用取值之类的,这个通常是放在组件的上下文中,然后内部通过上下文取。按照喜欢的方式自由发挥即可
js
//比如可以
const routes = [
{
parent: null,
path: "/",
fullPath: "/",
children: [
{
parent: //自己的上一级
path: "about",
fullPath: "/about"
}
]
}
]
const routeMap = [
{ parent: null, path: "", fullPath: "/" },
{ parent: "", path: "", fullPath: "/about" }
]
分别解释下为什么这么做
因为我们得能在路由进入的第一时间知道,有哪些路由会被用到,为了以最快的速度执行全部loader
函数,所以不允许抱着等组件加载到了就知道了的想法(vue 一开始就能知道,但 react 做不到)
fullPath
字段是用来做匹配用的,因为存在动态路由,所以不能通过===
的方式匹配,需要用到各个路由库提供的useMatch/matchPath
之类的方法来匹配
路由库匹配用到个路由的本质是,把fullPath
经过排序后,在转成正则表达式,然后挨个匹配。所以我们需要递归路由表,自己把每个层级的fullPath
全路径自己拼出来,然后传给库的xx match
方法去,让库的内部给编译成正则,用它们自己的方法来匹配,如果返回 true 就代表这个是入口
我们拿到的入口一定是从最终的子路由开始的,所以需要用到parent
字段递归的收集所有的loader
字段,然后一并执行
这里还得考虑怎么做区分,比如父组件拿到的一定是父loader
的数据,子组件拿到的一定是子loader
的数据,不做区分就混了,当然,觉得不需要区分这步就可以跳过了,主要是它们可能混了可能出现数据覆盖
要考虑的细节代表性的就这么多了,小坑自己踩吧,如果不知道怎么做,就看简单版本的,因为简单版本的会跳过许多步骤
- 所有层级拉平,就不需要收集loader列表了
- 如果是
vue
,路由守卫能直接知道进到哪个子路由了。如果是react
,请确保最顶层的路由,除了404
不能出现任何动态路由,然后直接===
就可以做到了 - 由于层级拉平了,所以不需要
parent / children / fullPath
字段了,也不需要递归,不需要做一个单独的routeMap
- 对于
react
,请确保只在最顶层加路由守卫相关的逻辑,这样就不需要处理缓存,集成,字段是不是最新的...乱七八糟的破情况 - 不知道怎么做数据共享和传递,可以直接搞个全局变量,记得不要挂载
windows
上,在文件内创建变量然后导出去即可,我们代码打包后都是在闭包中执行,你不给外边提供访问渠道外边是拿不到的 - ssr 不支持是因为这需要框架一边的协助,因为获取到的数据需要注入到 html 中不然客户端水合会报错,这个逻辑不能单纯写到中间件里,需要写到 ssr 渲染的函数中,在最后返回 html 时动态给混进去(肯定是组件渲染完才知道都有哪些数据,所以只能在最后混入),不过也无所谓,毕竟大多数业务还是后台管理系统,这已经够用了
总结
我只是给出一个思路,实现上来说可以复杂可以简单,代码并不通用所以我也就没必要给出具体代码了,大家可以尝试按照这个思路写一次就心中有谱了
个人觉得这个功能其实更适合由框架来做,而不是单一的某个库,比如umijs/remixjs
之类的,react-router
的数据路由其实已经提供了这个机制了,可是我用起来并不舒服,有些复杂,没中文,它其实是remixjs
团队成员做的,所以用了还得学它的框架思想(只用 loader 很多是没必要学的),不光有理解负担,打包的代码会倍增 ,但如果用我的方式做出来再怎么扩展撑死都没1kb
之所以了解,是因为做了个小框架,初版用react
单独做出来感觉还行,但和权限校验,懒加载,ssr 结合起来各种 bug 就层出不穷,真正做到代码5
分钟,调试1小时
扯远了,相关的东西和心得以后都会陆陆续续公布出来,喜欢的可以求个点赞,收藏,关注么~
往期文章