扯皮
在上班之前就有了解到 TanStack 系列的相关产品,尤其是 TanStack Query 比较出名,当时看了不少外国视频教程的介绍,后来国内相关文章也多了起来,但是一直没上手玩过:一是因为看完教程感觉存在一定使用门槛,二是强大的功能在业务当中一直没有用到,为了新技术用而用感觉完全没有必要🙃
包括这次要介绍的 TanStack Router 也是之前听说但是没有上手过,为什么这次突然想要玩玩这个呢?是因为元旦那天看到云谦大佬在内网发了一篇文章,介绍了最新开发的 TNF 框架:umijs/tnf,毕竟马力侧中台技术都依赖着这些蚂蚁大佬的基建,所以了解他们的技术动向个人认为还是很有必要的
文章内容有一部分介绍强推了 TanStack Router,根据他的描述发现这个库的能力确实比较逆天,正好自己这段时间业务不是很忙,就上手体验了一把
正文
这里不过多介绍基本使用,一是掘金已经有不少介绍的文章,二是b站也有入门视频教程,三是官方文档已经很细了,虽然全英但配合插件翻译基本无障碍阅读:Overview | TanStack Router React Docs,把里面的代码全照搬过来没什么意思
所以本文内容属于安利向不会贴很多代码,主要介绍一下我眼中的 TanStack Router,顺便附上部分官方文档的链接,感兴趣的同学自行研究
三大路由模式
这里的路由模式可不是指我们常说的 hash、history,而是 TanStack Router 文档中介绍的三个模式:基于文件的路由、虚拟文件路由、基于代码的路由
分别介绍一下这三种模式:
基于文件的路由
该方式是 TanStack Router 强推荐的用法,也是我看到现在所有教程都在讲的用法
主要特点在于指定项目目录下的 routes 文件夹(可配置),以文件约定的形式来创建路由,类似 Umi 和 Next 的约定式路由,比如这样:
这里的 __root
、.lazy
和 $
的使用都是约定的规则,相信大伙也都能猜出来什么意思,root 代表根路由,lazy 代表开启了路由懒加载,$ 则是动态路由
当然这只是入门的玩法,具体可以参考这部分内容:
基于文件的路由 | TanStack Router React 文档 --- File-Based Routing | TanStack Router React Docs
Route Trees & Nesting | TanStack Router React Docs
看到这你可能会觉得也没什么,约定式路由早就不是什么稀奇的东西了🙃,关键在于 TanStack Router 还提供了不同构建工具的插件,官方指定 Vite 插件👍:
File-Based Routing | TanStack Router React Docs
我们以 Vite 为构建工具,直接在项目中进行配置:
按照官方文档的入门配置完,pnpm run dev
把项目跑起来时会发现自动生成一个 routeTree.gen.ts
文件,里面组织所有的路由信息,它长这样:
在我们项目入口引入 TanStack Router 时会用到 routeTree.gen.ts
中生成的路由信息:
看着好像还是没什么🤔,关键来看我下面的几张动图:
很明显该插件会监听我们 routes 文件夹下的变动,根据约定的规则动态生成对应的路由代码模板,之后再重新生成 routeTree.gen.ts
文件,当然这个文件除了保存路由信息外还有另外一个功能,我们放到下一节说
这样用户每次创建页面就直接上手写业务代码了,路由配置全部自动化
这种文件路由的方式个人觉得只有一个问题:约定,可以看到 routes 下的文件都被监控,这就导致该文件下的写法全部都要按照 TanStack Router 的规则来
像我个人的习惯以及一些中后台系统通常都是以文件夹为页面组件,但是这种写法也都会把它当作路由生成配置了:
其实我只是想写一个页面下的子组件而已😶,所以为了按照它的约定只能换到其他位置了,目前还没看到能不能配置,但总而言之都需要为约定式路由进行一定的妥协
而且在我书写的过程中会遇到 routeTree.gen.ts
文件生成的内容不规范导致报错的问题,这个问题出现的还挺频繁的,感觉还是需要进一步优化
虚拟文件路由
刚才提到约定文件路由的约束不太方便,这不,虚拟文件路由就来了,它允许用户自定义路由规则来生成整个路由结构,看文档开头描述再清晰不过了:
Virtual File Routes | TanStack Router React Docs
写法上确实比较奇特,让用户自己手动配置路由路径与文件路径的映射,如果一个老项目的路由方案想要迁移到 TanStack Router 上获取是个不错的选择,当然应该是还有更棒的应用场景,还没仔细研究🤔
值得一提的是 TanStack Router 还支持文件路由和虚拟文件路由混用,不明觉厉😮:
Virtual File Routes | TanStack Router React Docs
基于代码的路由
个人觉得如果说基于文件的路由是自动挡,那基于代码的路由就是手动挡,文档里给了更专业的描述:
这种写法对于我们来说更熟悉一些,类似 vue-router 那样直接在一个文件下编写路由表配置,但是 TanStack Router 在编写上看着会麻烦很多:
首先需要使用对应 API 创建每个页面的路由信息:
之后再手动构建出路由 Tree:
而且有一个特别鸡肋的地方,在创建路由信息时必须配置 getParentRoute
字段指定对应的父路由,这可太难受了🙁
官方也给了解释,因为涉及到 TS 类型推断,虽然鸡肋但确实大有用处:
但注意这个 API:addChildren
,有了这玩意儿那中后台的动态权限路由可太好做了,回想之前 react-router... 只能说光想想都难受🤐
TypeScript 100%
官方号称 TanStack Router 有完整的类型提示,这也是我们作为用户在使用体感上最友好的一点了,下面直接动图进行展示,顺便再展示一下 TanStack Router 的一些基础用法,大伙可以跟 react-router 做个对比
- Link 组件 to 属性配置:
- 动态路由传参和获取:
- 路由跳转:
这体验可太好了,这都归功于 TanStack Router 内部实现的复杂类型系统以及插件实时生成的路由信息🧐
很早之前用路由的时候我就经常吐槽为什么没有一个路由库能够做到类型提示呢?事实证明 TanStack Router 确实做到了👍
可以看到 TanStack Router 为了有类型提示也是做很多妥协,比如我们经常动态路由传参 /:postId
直接无脑字符串拼接了,而这里只能以额外的 params 属性进行传入,但这都无伤大雅,有类型提示什么都好说
Preloading
preloading 这个词对于我们来说并不陌生,比较常见在 HTML link 标签里,通过设置 <link rel="preload">
来表示要预加载资源
同样在图片加载时也会用到这项技术,提前访问该图片资源至本地,等真正访问时就走缓存快速加载出图片
那么路由也一样,TanStack Router 支持三种预加载策略: Preloading | TanStack Router React Docs
我们来写两个 lazy 路由测一下:
这种预加载技术能够快速加载出我们即将访问的页面,提高用户的体验,而且内部会进行数据缓存,具体配置参考文档
请求瀑布问题
请求瀑布的话题比较大,在 TankStack Query 中有详细介绍这个问题并提出部分解决方案:
Performance & Request Waterfalls | TanStack Query React Docs
这里就拿文章中举的例子简单过一下,我们思考这个场景,一个文章详情页被划分为三个部分:
当我们来到详情页时会根据文章 id 获取文章详情,而文章评论同样也需要进行获取,所以我们可能会写出这样的代码(为了简化代码先使用 useRequest 代替正常请求写法):
很棒,这时候我们就写出了一个典型的请求瀑布案例,文章详情请求与文章评论请求属于串行关系:
发送获取文章详情请求 => loading... => 挂载 comment 组件 => 发送获取评论内容请求 => comment loading... => 渲染评论内容
然而仔细观察会发现两个请求参数都是使用的 articleId,那为什么还要串行呢?
我们完全可以把它们改为并行,来加快整个页面内容加载
这样优化就完了么?🤔我们再思考一下, useRequest 的底层实际上也是在 useEffect 即界面渲染后发送请求,事实上我们大部分场景下都是在这里发送的,但如果想要再进一步进行优化呢?
那就是使用路由的 loader,将页面中挂载时需要获取的数据都迁移至 loader 中
在 umi 中也有类似的实践,具体参考这部分内容,介绍的很详细:路由数据加载
只不过很少人在用,甚至我在内网文档进行全局搜索也只是搜到了 umi 官方文档对这个 API 的介绍😅
而在 TankStack Router 的数据加载部分有详细介绍 loader 的使用,以及配合 TankStack Query 做预获取数据操作,这里就不过多举例子了:
数据加载 | TanStack Router React 文档 --- Data Loading | TanStack Router React Docs
Prefetching & Router Integration | TanStack Query React Docs
Stale-While-Revalidate
Stale-While-Revalidate 简称 SWR,是一种缓存策略,简单描述一下应用场景:
当初次访问页面时发送请求拿到数据并将其进行缓存,下次访问时直接从缓存中拿数据展示视图,而内部再发送请求获取最新鲜的数据,当然这里存在缓存时间以及数据过期时间(新鲜)的配置
这种技术主要应对的是对数据实时性要求不高的场景,比如一些官网比较固定的数据列表展示:
normal:发送请求 => loading... => 拿到数据渲染视图,从其他页面回到列表页是会重复上述过程,每次的 loading 展示挺搞人心态的,因为获取的数据列表内容大部分情况下都一样😑
SWR:发送请求 => loading... => 拿到数据渲染视图并缓存数据,从其他页面回到列表页:读取缓存数据渲染视图 => 根据数据是否新鲜来决定内部发送请求 => 更新缓存数据
这种策略不仅提高用户的体验还减轻了服务端压力,像 ahooks、useSWR、TanStack Query 都有相关实践
但这些都是在数据请求上的 SWR,而在 TanStack Router 中也有:Data Loading | TanStack Router React Docs
我们来写一个接口实践一下看看效果,这里我故意设置随机值保证每次请求获取的数据不一样:
在 about 页面的 loader 里调用接口,使用 useLoaderData 来加载数据,注意需要手动设置 staleTime 过期时间:
来看看视图效果,我们频繁切换 Home 与 About 页面观察请求的次数,可以明显看到 About 页面下 loader 中加载的数据是有做缓存的:
使用缓存的好处不必多说,但同样也带来了一些心智负担,多加一层就意味着就需要你多操一份心去管理,所以使用利弊还是需要自己权衡😇
End
更多玩法请直奔 TanStack Router 官方文档,本来这部分内容是想在组内周会上做一次简单分享的,后来突然想到数码这边的中后台都收敛到 umi 里了
由于内置的是 react-router,路由方案不像状态管理或其他工具一样想换就能换的,所以分享了也没法进行实践,哥们就是一个写业务的,只能祈祷蚂蚁的大佬们速速引进这个库了🤷♂️
业务上给我们带来的启发更多的是关于请求瀑布那部分,后续准备在我们内部的项目实践一下 Stale-While-Revalidate,到时候再单独记一次优化吧