前端路由
web 路由、服务端路由 or 前端路由
web 路由
web 路由(router)指解析 url,并根据路由表中 url 和预设函数的映射关系(route),返回用户相应的结果,也可以理解为一系列映射关系本身。
根据解析 url 解析的位置,可以将路由分为服务端路由和前端路由。
(这个基于 url 的定义并不严谨,比如 memory 模式的前端路由就不依赖于 url,也许说是映射的分发更合适?)
服务端路由
url -> 资源
服务端监听到请求时,解析请求的 url,返回/处理相应资源。“对于最简单的静态资源服务器,可以认为,所有URL的映射函数就是一个文件读取操作。对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理,等等。”
优点:
- 安全性(?这里不太理解,暂且记着);
- SEO 友好;
缺点:
- 等待响应可能会有延迟,影响用户体验;
- 服务器压力大;
前端路由
url -> 组件
“对于客户端(通常为浏览器)来说,路由的映射函数通常是进行一些 DOM 的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。”
优点:
- 不需要请求并等待响应,快 + 服务端压力小;
缺点:
- SEO 不友好;
- 前进、后退时没有有效利用缓存;
单页面应用程序(SPA, Single Page Application)
单页面应用程序(SPA, Single Page Application),是指整个应用或网站只有一个完整的 html 页面作为容器,在交互时动态地修改这个 html。
与之区别,对应的模式还有:
- 多页面应用程序(MPA, Multi-page Application):每次更新时,服务端都会提供一个新的 html;
- 渐进式 web 应用(PWA, Progressive Web Apps):大致是将 web 应用赋予了原生应用的能力,利用 sevice woker 实现离线缓存和消息推动,利用 manifest.json 解决一级入口(不用打开浏览器);具体没看懂,不细写了:developer.mozilla.org/zh-CN/docs/…
前端路由和常用模式
hash 模式
因为 url 的 hash 部分修改的时候,并不会向服务端发出请求,可以根据监听 window 对象的 hashChange 事件,获取 hash 值并进行相应的 dom 操作。
优点:
- 支持浏览器前进后退;
- 兼容性好;
缺点:
- url 里会有‘#’字,不美观;
- 后端无法获得 url 中相关数据;
history 模式
H5 History API 可以在不刷新页面的情况下,操作浏览器的历史记录(developer.mozilla.org/en-US/docs/… 。history 模式的前端路由主要用到了其中的 history.pushState()和history.repalceState() 两个操作,以及 onpopstate 事件。
- 两个操作本身不会向服务端发送请求,和 hash 一样,只是 url 的一个 state;
- 不会触发 hashChange 和 popstate 事件;
参考:
不过,原生的 history 只在back、forward、go时会触发 popstate 事件,pushState 和 repalceState 并不会触发事件,可以通过替换 history.pushState 方法触发自定义事件解决;(测试偷懒加了一个 go(0) 强制重新加载触发事件)。
history 模式也需要改造服务端,比如直接输入 url 为 ‘www.xxx.com/abc’ 时,服务端收到的请求也是 ‘www.xxx.com/abc’ ,却找不到和 ‘abc’ 匹配的资源,就会报错。所以要让‘www.xxx.com/abc’ 也指向默认资源。如果 url 是‘www.xxx.com/#abc’ 的 hash 形式,服务端收到的请求实际上是 ‘www.xxx.com’ ,就不会有问题。
优点:
- 直观、正式、符合规范;
- 重定向至默认资源时,服务端也可以感知参数;
缺点:
- 需要改造服务端;
memory 模式
hash 和 history 都是依赖 url 实现的,而 memory 模式不依赖于 url,而是依赖于 web 本地存储(window.localStorage)。
因为不改变 url,memory 模式的路由无法分享,所以只能用在单机应用里。同时对于浏览器前进、后退的管理比较复杂。
momery 模式现存于 RN 中。
web 发展与路由方式
来源:blog.csdn.net/nihaio25/ar… ;
web 1.0 时期,开发整个 HTML 页面都是由服务器进行渲染的(SSR), 这个时期自然只有服务端路由:
随着 ajax 的出现,开始了前后端分离的开发模式,Single Page Application 成为流行,后端只在前端请求时提供资源、不再负责拼接 html。所以服务端路由和前端路由并存: (前端服务器是什么:www.jianshu.com/p/d19af5649… ,前端服务器的路由应该也算是服务器的路由吧)
不过 SPA 对 SEO 不友好,所以又出现了新的服务器渲染:
(SSR 这部分有空可以去看一下 next.js)
react-router
react-router 实现前端路由
react-router v6 源码
源码分析文章,图和源码都来自于这几篇文章:
简单原理:
-
react-router v6 依赖第三方库 history 管理会话历史,可以支持 hash、history 和 memory 模式,history 定义了一个事件中心,在触发 push、go 时,调用原生 history api 更改浏览器路由地址,并触发自定义事件;
-
BrowserRouter(入口组件)监听 history 变化,并触发 setState,更新当前的 location 和 action(history 变化的来源:'POP' | 'PUSH' | 'REPLACE'),state 传入 Router 组件;
-
Router 提供 NavigationContext 和 LocationContext 两个上下文;
-
Routes 作为 BrowserRouter 提供给 Router 的子组件,处于 Router 提供的上下文中。内部使用 useRoutes 渲染;
其中 createRoutesFromChildren(children) 把 Route 子元素展开成为数组;
-
useRoutes 根据 location 上下文,先寻找到与当前 location 对应的路由对象(matchRoutes -> matches),再渲染为 react element(renderMatches);
-
matchRoutes 先把所有路由规则打平为一个一维数组,并且给每个路由规则打分,按分数降序排序后,选择第一个匹配的路由规则作为最终采用的规则;
-
matches 上记录了 pathname、element 和子路由规则等;
-
renderMatches 从右至左遍历 matchs,将 Route 中提供的 element 进一步封装,并将遍历上一步得到的封装结果(overlay)作为 RouteContext 上下文提供给 elem;所以用户可以使用 overlay 元素,在父路由规则对应的元素中渲染子路由对应的元素;
-
useNavigate 若收到 number 类型参数,调用 history.go 方法,若是字符串或者对象,处理后调用 history.replace 方法;
-
link 元素是对原生 a 元素的封装,禁止了原生事件,所以点击跳转时也会触发 react-router 中的逻辑;