你应该考虑放弃 react-router 的数据路由模式,改而使用更加适合国内版本的封装版本(包含完整可 CV 的模版)

你应该考虑放弃 react-router 的数据路由模式,改而使用更加适合国内版本的封装版本(包含完整可 CV 的模版)

你应该考虑放弃 react-router 的数据路由模式,改而使用更加适合国内版本的封装版本(包含完整可 CV 的模版)

你应该考虑放弃 react-router 的数据路由模式,改而使用更加适合国内版本的封装版本(包含完整可 CV 的模版)

2 人赞同了该文章

你应该考虑放弃 react-router 的数据路由模式,改而使用更加适合国内版本的封装版本(包含完整可 CV 的模版)

 

React 的路由库

React 只有核心是官方的,Nextjs 可以算半个官方的。这会造成什么问题呢?

那就是使用上的撕裂感,和各种相关库的野蛮生长。好处是有了更多的选择,坏处是实现上可能存在冗余,以及会对很多编码能力不强的同学造成很大的困扰

能叫上名的 React 路由是有好几个的,但我们最熟悉,国内用的最普遍的是 react-router,它现在整合了 remixjs 的路由包,对外的名称叫 react-router-dom,相当于三个库的结合体,也是本篇文章讲解的对象

 

数据路由

数据路由被叫做 data-router,核心功能都是从 remix-router 里引进来,它带来了许多的新特性,例如懒加载,最快的数据加载 loader 机制form-action 的交互模式等等

这里我们比较喜欢的是,如下开箱即用的功能

  1. 懒加载
  2. 返回路由匹配项 —— 通过 hooks 函数直接获取到进入的是哪些路由,对于鉴权之类的功能,很有帮助
  3. Loader 机制 —— 可以让进入的路由项并发的执行一个函数,通用用于数据加载的提速
  4. Promise 的组件化使用
  5. 围绕路由项周边的辅助功能,比如路由进入异常可以给个友好提示

这还只是个别功能,其他强大的功能也不少。看似美好的表面藏着的,却是国内项目很难用到有甩不掉的包袱,即它提供的新功能我们可能只能用到一小部分,加上也许会用到的撑破天应该都不足一半,如果只是盲目的按照官方推荐方式使用,那就得把另一半用不到的内容也带上

为了那一点点 easy 的功能,却会使得你的应用体积暴增

显然这是不划算的,我们只需要小小的封装也可以做到类似事情,本篇文章都会给你讲到

至于为什么我们用不上官方还推呢?也许国外的大佬们更喜欢吧

 

打包数据对比

图片太多我这里只把我测试的,数据、方式、结果,简单的贴出来,感兴趣的可以自己尝试

测试步骤为

  1. 使用 vite 创建一个 ts-react 应用
  2. 安装 react-router-dom@^6.22.2,这是测试时能安装到的最新版本
  3. 创建 3 个子路由,代码在下边
  4. 同样的功能分别用,数据路由和非数据路由的方式实现
  5. Dev 测试切换无报错,然后打包对比体积

子路由代码

//类似于这样的代码,写三个,作为三个页面
export const Home = () => {return <div><h1>home</h1></div>
}

数据路由

// 文件路径 src/DataRouter.tsx
import { createBrowserRouter } from "react-router-dom"
import { Home } from "./pages/Home"
import { Page1 } from "./pages/Page1"
import { Page2 } from "./pages/Page2"
import { App } from "./App"
​
// App 组件是全局的根组件
export const DataRouter = createBrowserRouter([{path: "/",element: (<App><Home /></App>)},{path: "page1",element: (<App><Page1 /></App>)},{path: "page2",loader: () => 1,element: (<App><Page2 /></App>)}
])
​
// App.tsx
import { FC, ReactNode } from "react"
import { Link } from "react-router-dom"
​
export const App: FC<{ children: ReactNode }> = ({ children }) => {return (<div><div className="nav" style={{ display: "flex", gap: "1rem" }}><Link to="/">to home</Link><Link to="/page1">to page1</Link><Link to="/page2">to page2</Link></div><hr />{children}</div>)
}
​
​
// 入口文件 main.ts
import React from "react"
import ReactDOM from "react-dom/client"
import { DataRouter } from "./DataRouter.tsx"
import { RouterProvider } from "react-router-dom"
​
ReactDOM.createRoot(document.getElementById("root")!).render(<React.StrictMode><RouterProvider router={DataRouter}  /></React.StrictMode>
)
​

 

一般写法,非数据路由

//App.tsx 和上边一样
//main.tsx 
import ReactDOM from "react-dom/client"
import { App } from "./App.tsx"
import { BrowserRouter, useRoutes } from "react-router-dom"
import { Home } from "./pages/Home.tsx"
import { Page1 } from "./pages/Page1.tsx"
import { Page2 } from "./pages/Page2.tsx"
import React from "react"
​
const AppRoutes = () => {return useRoutes([{path: "/",element: <Home />},{path: "/page1",element: <Page1 />},{path: "/page2",element: <Page2 />}])
}
​
ReactDOM.createRoot(document.getElementById("root")!).render(<React.StrictMode><BrowserRouter><App><AppRoutes /></App></BrowserRouter></React.StrictMode>
)

 

打包结对对比

会发现只是单纯写了个路由,什么都没干,体积膨胀了 36.13kb

如果继续使用相关的 hooks 体积还会继续膨胀

演示中 App 组件是用来做全局包裹的组件,这里可以给一些公共的样式,依赖注入一些数据等,几乎是必备的,会发现数据路由下用起来很麻烦

如果你可以接受那就可以关闭这篇文章啦~

//数据路由
dist/index.html                  0.40 kB gzip:  0.27 kB
dist/assets/index-98d03929.js  162.97 kB gzip: 53.17 kB
built in 961ms
//一般写法
dist/index.html                  0.40 kB gzip:  0.27 kB
dist/assets/index-2708b45c.js  199.10 kB gzip: 64.82 kB
built in 991ms

 

 

 

 

整理下路由需要哪些功能

这里拿 vue-router 作对比,它足够的成熟,适用范围足够的广,这是全球开发者共同印证的事实

直接说结论,为了满足我们日常所需以下功能几乎是必备的

  1. 懒加载
  2. 生命周期/路由守卫
  3. 提供对外做动画的出口
  4. 组件内得能获取到路由的信息,越多越好

对于不适用数据路由的 react-router 来说

1,2 不提供,3,4 支持,但 4 刻意做的很有限(和 vue-router 比起来提供的信息少很多很多)

我们的目标也很明确,把 1,2 自己手动实现即可

至于其他的都是些锦上添花的功能,比如把 Promise 组件化之类的

懒加载

惰性加载分为两步

  1. 惰性路由的代码单独打包,这是打包工具都具备的基本能力,只要用 import() 引入就会生效
  2. 返回的结果是 Promise,框架得能解析,或者能根据开发者自定义的返回,正常运行

React. Lazy 是官方提供的,用它做个简单就够了

const LazyLoad = (loader: () => Promise<any>) => {Const Element = lazy (loader)Return () => (<Suspense><Element /></Suspense>)
}

使用也很简单

LazyLoad (() => import ("./pages/Home"))

 

 

生命周期 / 路由守卫

生命周期想要实现的完整会非常的复杂,结合我们经常使用的场景来说,只有全局进入 是刚需,即当进入页面时,只有当符合要求了才能正常的加载显示页面,否则就给重定向到别的地方

路由也是由一个个 React 组件组成的,所以符合一般组件的加载顺序,即从上到下,一层一层顺序创建,我们想做个卡点其实相当于我们需要给每个路由创建一个父组件,动态的判断是否要显示真正的路由组件

这里有个技巧,就是我们没有必要给每个路由都创建个父组件,只需要给每条路由的那个祖先组件创建一个就行了,例如

<Route name="祖先1" element={<Comp />}><Route name="爷爷">...</Route>
</Route>
<Route name="祖先2" element={<Comp />}><Route name="爷爷">...</Route>
</Route>

这是伪代码,里边每个最外层的就是组件组件,然后它可以嵌套,感觉会像链子一样

给祖先组件创建组件,是可以使用 useLocation 钩子的,有了它就能知道进来的 patname 路由路径,即便是深层次的子路由路径变化,也是能触发的。所以给祖先组件套一层,就能监控到所有的路由变化

我们可以简单的封装出以下结构

Export interface GuardRouteProps {Element: FC<any>
}
export const GuardRoute: FC<GuardRouteProps> = ({ Element }) => {Const { pathname } = useLocation ()const [RouteComp, setRouteComp] = useState<FC>(() => null) Const nav = useNavigate ()UseEffect (() => {If (RouteComp === Element) return// todo... 路由守卫的校验逻辑//通过 setRouteComp (Element)//不通过 nav ("/login")}, [])return <Element />
}

 

获取路由信息

这样做有个问题

我们不知道哪个路由,是否应该用什么策略来干些不同的事,比如页面 A 和页面 B 的校验逻辑不同,只知道一个路由路径我们无法做差异化配置

这里就可以做个 map 备用,对于路由的定义我们写出以下代码

Type GRouteObject = RouteObject & {meta?: Record<string, any>
}
Export const routes: GRouteObject[] = [{Path: "/",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Home"))} />},{Path: "/page 1",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Page 1"))} />},{Path: "/page 2",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Page 2"))} />,}
]
​
Const mapRoutesToMap = (Routes: GRouteObject[],map: Map<string, GRouteObject>
) => {Routes.ForEach (route => {Map.Set (route. Path!, route)If (route. Children) {MapRoutesToMap (route. Children, map)}})Return map
}
Export const routesMap = mapRoutesToMap (routes, new Map ())

吐槽:react-router-dom 的 ts 类型声明连个扩展都不给,还得自己定义,人家 Vue 直接给了个 meta 给用户使用

之所以吐槽是因为,我们开发者是不知道将来的某个版本中,我们使用的某个字段是否会被库作者使用,反之 vue-router 明确给了个 meta 字段就代表它只会被用于用户使用,就不会产生可能的意外冲突

这里我们扩展出一个 meta 字段用户写一些差异化的配置

制作这个 routesMap 并不会拉胯性能,它会递归的把嵌套路由拉成一级

它能帮助封装的路由守卫获取差异化配置,对于后台管理系统的左侧菜单也会有帮助

Export interface GuardRouteProps {Element: FC<any>
}
export const GuardRoute: FC<GuardRouteProps> = ({ Element }) => {Const { pathname } = useLocation ()const [RouteComp, setRouteComp] = useState<FC>(() => null) Const nav = useNavigate ()//react-router-dom 会帮我们把 query/hash/多余的符号去掉,只要不是动态参数是一定能够匹配到的Const { meta = {} } = routesMap.Get (pathname)UseEffect (() => {If (RouteComp === Element) return// todo... 路由守卫的校验逻辑//通过 setRouteComp (Element)//不通过 nav ("/login")})return <Element />
}

 

完整代码

到此一个很普通的模版就做好了,我们规整下就可以 cv 直接用了

//router. Tsx
Import { FC, Suspense, lazy, useEffect, useState } from "react"
Import {RouteObject,UseLocation,UseNavigate,UseRoutes
} from "react-router-dom"
​
Export interface GuardRouteProps {Element: FC<any>
}
export const GuardRoute: FC<GuardRouteProps> = ({ Element }) => {Const { pathname } = useLocation ()const [RouteComp, setRouteComp] = useState<FC>(() => null)Const nav = useNavigate ()//react-router-dom 会帮我们把 query/hash/多余的符号去掉,只要不是动态参数是一定能够匹配到的Const { meta = {} } = routesMap.Get (pathname)UseEffect (() => {If (RouteComp === Element) return// todo... 路由守卫的校验逻辑//通过 setRouteComp (Element)//不通过 nav ("/login")})return <Element />
}
​
const LazyLoad = (loader: () => Promise<any>) => {Const Element = lazy (loader)Return () => (<Suspense><Element /></Suspense>)
}
​
Type GRouteObject = RouteObject & {meta?: Record<string, any>
}
Export const routes: GRouteObject[] = [{Path: "/",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Home"))} />},{Path: "/page 1",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Page 1"))} />},{Path: "/page 2",element: <GuardRoute Element={LazyLoad(() => import ("./pages/Page 2"))} />}
]
Export const useRouterRoutes = () => routes
​
Const mapRoutesToMap = (Routes: GRouteObject[],map: Map<string, GRouteObject>
) => {Routes.ForEach (route => {Map.Set (route. Path!, route)If (route. Children) {MapRoutesToMap (route. Children, map)}})Return map
}
Export const routesMap = mapRoutesToMap (routes, new Map ())
Export const useRoutesMap = () => routesMap
​
Export const AppRoutes = () => {Return useRoutes (routes)
}
​
// main. Tsx
Import ReactDOM from "react-dom/client"
Import { App } from "./App. Tsx"
Import { BrowserRouter } from "react-router-dom"
Import { AppRoutes } from "./router. Tsx"
​
ReactDOM.CreateRoot (document.GetElementById ("root")!). Render (<BrowserRouter><App><AppRoutes /></App></BrowserRouter>
)
​

 

其他功能 —— 动态参数,SSR,Loader

动态参数目前实现不支持,尽量用 query 参数代替即可,如果需要可以做以下扩展

export const GuardRoute: FC<GuardRouteProps> = ({ Element }) => {Const { pathname } = useLocation ()const [RouteComp, setRouteComp] = useState<FC>(() => null)Const nav = useNavigate ()//react-router-dom 会帮我们把 query/hash/多余的符号去掉,只要不是动态参数是一定能够匹配到的
  Let route: GRouteObject | undefined = routesMap.Get (pathname)If (! Route) {Const path = Array.From (routesMap.Keys ()). Find (path => !!UseMatch (path))If (! Path) {// 说明没有一个兜底的,自己看着办
      Throw "说明没有一个兜底的,自己看着办"}Route = routesMap.Get (path)}
UseEffect (() => {If (RouteComp === Element) return// todo... 路由守卫的校验逻辑
    //通过 setRouteComp (Element)
    //不通过 nav ("/login")
  })return <Element />
}

useMatch 会拿我们自己的路径,经过编译再和实际的路由路径对比,它可以匹配动态路径参数

如果你能理解并且能靠自己手写出来这个结构,那么你可以尝试看看我的另一篇文章,尝试自己实现个 loader 机制

这里只是个半成品代码,因为存在匹配顺序的问题,觉得复杂的话就用 query 代替所有动态路径即可,这是百分百可以轻松做到的

标题是:如何设计并实现一个所谓"全网最快的路由数据加载 loader" —— vue/react

地址

usagisah:如何设计并实现一个所谓"全网最快的路由数据加载 loader"的思路 —— vue/react0 赞同 · 0 评论 文章

 

总结

本文给出的模版直接 CV 就足够日常所需了,扩展起来也容易

因为自己的封装中没有用到数据路由相关的 api,经过打包工具 treeshaking 后就不会携带这些死代码,最后给出完整的打包对比

/* 
Dist/index. Html                  0.40 kB │ gzip:  0.27 kB
Dist/assets/index-98 d 03929. Js  162.97 kB │ gzip: 53.17 kB
✓ built in 961 ms

Dist/index. Html                  0.40 kB │ gzip:  0.27 kB
Dist/assets/index-2708 b 45 c. Js  199.10 kB │ gzip: 64.82 kB
✓ built in 991 ms



Dist/index. Html                  0.40 kB │ gzip:  0.27 kB
Dist/assets/Error-0 cc 7 b 8 e 1. Js    0.11 kB │ gzip:  0.12 kB
Dist/assets/Page 1-3 e 256 cac. Js    0.12 kB │ gzip:  0.12 kB
Dist/assets/Page 2-319 ed 62 d. Js    0.14 kB │ gzip:  0.14 kB
Dist/assets/Home-e 8 de 57 d 5. Js     0.16 kB │ gzip:  0.15 kB
Dist/assets/index-8 de 41 b 2 e. Js  164.70 kB │ gzip: 53.93 kB
✓ built in 1.00 s
*/

最下边是经过自己封装后打包的体积,体积碰撞的范围非常有限,和使用了数据路由后的代码对比还是相当明显的

如果细化的话,我们封装的代码其实连 1kb 都没,ts 代码不会打包进去,膨胀的代码主要来源于项目代码+ react router 相关钩子的代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/907247.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于CSU8RP1186芯片的握力器解决方案

握力器方案采用高精度传感器、ADC芯片和先进的数据处理技术,可将物体的重量以千克和磅为单位进行准确测量和记录,其原理是通过在称重时,握力器传感器的金属构架受力形变,贴片上的金属丝也随着被拉长或缩短,金属丝…

深入解析:C++ 内存管理:从底层原理到实战应用

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

sass踩坑:@import导致前端项目打包体积膨胀

sass踩坑:@import导致前端项目打包体积膨胀项目打包时发现了css代码出现了大规模膨胀,后面排查发现是scss的@import这个api问题。@import会把所有import的css横向展开,拷贝一份,导致打包后css代码量越来越大。 所以…

mjs和mts

mjs和mtsmjs和mts漫思

亮相2025年服贸会,天翼云打造高质量算力服务新生态!

近日,2025年中国国际服务贸易交易会(简称服贸会)在北京隆重举行。本届服贸会以“数智领航,服贸焕新”作为年度主题,顺应服务贸易数字化、智能化、绿色化趋势,聚焦人工智能、医疗健康、智慧物流、商旅文体健融合发…

易路薪酬专家Agent:基于10亿级数据与AI的智能薪酬解决方案

导读: 在AI深度赋能人力资源管理的趋势下,薪酬模块的智能化已成为企业提升人效与战略决策的关键。本文深度解析易路人力资源科技公司最新推出的人才薪酬专家Agent,重点介绍其基于10亿级动态市场数据与多智能体协同(…

有点意思!Java8后最有用新特性排行榜!

相信这两天看了 JDK 25 新特性的同学已经彻底对 Oracle 失望了,这那是挤牙膏啊?是连牙膏都懒得挤了。 所以,大家都在评论区喊话,如果你(Oracle)实在不想发可以不发,但不要糊弄大家。 那么,今天呢。我也把从 JD…

数据结构 Trick 之:KDT 求 k 近/远 点

注意,此 Trick 的时间复杂度是错的,但是貌似目前没人能卡满。 能够解决的问题\(O(n \sqrt n)\) 可过。 维护二维平面。 每次求到一个点的 \(k\) 近或 \(k\) 远点。 \(k\) 很小(\(20\) 左右)思路 二维空间想到 KDTr…

.NET 8程序配置版本及产品信息

一、给主程序单独添加配置 1、双击主程序,会打开主程序的.csproj文件,在PropertyGroup下添加 <Company>Your Company</Company><Product>Your Product</Product><Version>1.2.3<…

C语言第二讲:进制转化

C语言中进制转化的符号表示进制 数据类型 赋值格式二进制 %0b a=0b1010八进制 %o a=03344十进制 %d a=1234十六进制 %x/%X a=0x34a5 / 0X43D6输出时转化: int a=100; printf("%o",a); 赋值时转化: int a;…

深入解析:Java 设计模式之桥接模式(Bridge Pattern)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

XXL-JOB(4)

XXL-JOB(4)分片任务 分片任务能更好的利用集群的能力,可以同时调度多个机器并行运行任务。分片任务的实现原理包括以下几个核心步骤:1、任务分配当一个分片任务被触发时,调度器会根据任务的分片参数决定需要多少个…

QOJ #10485. Peculiar Protocol 题解

Description 你有一个序列 \(a_1, a_2, \dots, a_n\),以及两个参数 \(d, r\)。 你可以做如下操作若干次:每次选择一段区间,使得他们的和可以被表示成 \(k \times d + r\) 的形式,其中 \(k\) 是一个非负整数。 你把…

C++ 常用关键字

1. static 控制作用域、生命周期或类成员归属 // 1. 全局/命名空间:仅当前文件可见(避免跨文件重定义) static int global_static = 10; // 其他文件无法通过 extern 访问// 2. 局部变量:生命周期延长至程序结束(…

vim 入门教学2

vim 入门教学2normal模式 normal模式markm{a-zA-Z} 给当前行做标记{a-zA-Z},如果使用大写字母可以跨文件跳跃 {a-zA-Z} 将光标跳转到{a-zA-Z}的行首跳转到上次使用跳转的位置例子: ma 将当前行标记为a a 跳转到a标…

【AP出版】第四届数理统计与经济分析国际学术会议 (MSEA 2025)

第四届数理统计与经济分析国际学术会议 (MSEA 2025)将于2025年12月05-07日在中国广州召开。【高录用快见刊:最快一周内即可录用,会后3-4个月见刊】 【征稿范围:数理统计、经济分析大方向主题均可收稿】 第四届数理统…

数据结构 Trick 之:区间子区间计数

能够解决的问题\(O(n \log n)\) 可过。 维护数列,无修改,每次查询一个区间的所有子区间。 离线思路 看到一个区间的所有子区间这种查询,直接做显然是做不了的。 考虑离线,那么将询问区间进行右端点排序,然后就可以…

mapstruct.Mapper|Mapping详解

------------------------------------------------------------------------------------------ org.mapstruct.Mapper 和 org.mapstruct.Mapping 是 MapStruct 框架中的核心注解,用于实现 Java 对象之间的自动映射。…

抽象代数-学习笔记

主要积累一些遇到的例子、题目。不定时更新。 运算有结合律的运算:普通/复数/矩阵/模意义下加法、乘法,映射复合,与或异或/集合相关, min/max。 仅仅满足部分群公理:\(\mathbb{N}^*, \mathbb{N}\)。\(\{0,1,2\}\)…

如何在保证质量的前提下,快速完成一份 PPT?

这是一个非常经典且普遍的问题,尤其对于产品经理、咨询顾问等角色来说,PPT既是生产力工具,也是时间吞噬黑洞。你能意识到这个问题并寻求解决方案,已经领先了很多人。 在保证质量的前提下快速完成PPT,绝非单纯追求…