[杂七杂八的学习记录] 前端路由 & react-router

335 阅读6分钟

前端路由

web 路由、服务端路由 or 前端路由

web 路由

web 路由(router)指解析 url,并根据路由表中 url 和预设函数的映射关系(route),返回用户相应的结果,也可以理解为一系列映射关系本身。

根据解析 url 解析的位置,可以将路由分为服务端路由和前端路由。

(这个基于 url 的定义并不严谨,比如 memory 模式的前端路由就不依赖于 url,也许说是映射的分发更合适?)

服务端路由

url -> 资源

服务端监听到请求时,解析请求的 url,返回/处理相应资源。“对于最简单的静态资源服务器,可以认为,所有URL的映射函数就是一个文件读取操作。对于动态资源,映射函数可能是一个数据库读取操作,也可能是进行一些数据的处理,等等。”

优点:

  1. 安全性(?这里不太理解,暂且记着);
  2. SEO 友好;

缺点:

  1. 等待响应可能会有延迟,影响用户体验;
  2. 服务器压力大;

前端路由

url -> 组件

“对于客户端(通常为浏览器)来说,路由的映射函数通常是进行一些 DOM 的显示和隐藏操作。这样,当访问不同的路径的时候,会显示不同的页面组件。”

优点:

  1. 不需要请求并等待响应,快 + 服务端压力小;

缺点:

  1. SEO 不友好;
  2. 前进、后退时没有有效利用缓存;

单页面应用程序(SPA, Single Page Application)

单页面应用程序(SPA, Single Page Application),是指整个应用或网站只有一个完整的 html 页面作为容器,在交互时动态地修改这个 html。

与之区别,对应的模式还有:

  1. 多页面应用程序(MPA, Multi-page Application):每次更新时,服务端都会提供一个新的 html;
  2. 渐进式 web 应用(PWA, Progressive Web Apps):大致是将 web 应用赋予了原生应用的能力,利用 sevice woker 实现离线缓存和消息推动,利用 manifest.json 解决一级入口(不用打开浏览器);具体没看懂,不细写了:developer.mozilla.org/zh-CN/docs/…

前端路由和常用模式

hash 模式

因为 url 的 hash 部分修改的时候,并不会向服务端发出请求,可以根据监听 window 对象的 hashChange 事件,获取 hash 值并进行相应的 dom 操作。

优点:

  1. 支持浏览器前进后退;
  2. 兼容性好;

缺点:

  1. url 里会有‘#’字,不美观;
  2. 后端无法获得 url 中相关数据;

image.png

history 模式

H5 History API 可以在不刷新页面的情况下,操作浏览器的历史记录(developer.mozilla.org/en-US/docs/… 。history 模式的前端路由主要用到了其中的 history.pushState()和history.repalceState() 两个操作,以及 onpopstate 事件。

  1. 两个操作本身不会向服务端发送请求,和 hash 一样,只是 url 的一个 state;
  2. 不会触发 hashChange 和 popstate 事件;

参考:

image.png

image.png

不过,原生的 history 只在back、forward、go时会触发 popstate 事件,pushState 和 repalceState 并不会触发事件,可以通过替换 history.pushState 方法触发自定义事件解决;(测试偷懒加了一个 go(0) 强制重新加载触发事件)。

image.png

history 模式也需要改造服务端,比如直接输入 url 为 ‘www.xxx.com/abc’ 时,服务端收到的请求也是 ‘www.xxx.com/abc’ ,却找不到和 ‘abc’ 匹配的资源,就会报错。所以要让‘www.xxx.com/abc’ 也指向默认资源。如果 url 是‘www.xxx.com/#abc’ 的 hash 形式,服务端收到的请求实际上是 ‘www.xxx.com’ ,就不会有问题。

优点:

  1. 直观、正式、符合规范;
  2. 重定向至默认资源时,服务端也可以感知参数;

缺点:

  1. 需要改造服务端;

memory 模式

hash 和 history 都是依赖 url 实现的,而 memory 模式不依赖于 url,而是依赖于 web 本地存储(window.localStorage)。

因为不改变 url,memory 模式的路由无法分享,所以只能用在单机应用里。同时对于浏览器前进、后退的管理比较复杂。

momery 模式现存于 RN 中。

web 发展与路由方式

来源:blog.csdn.net/nihaio25/ar…

web 1.0 时期,开发整个 HTML 页面都是由服务器进行渲染的(SSR), 这个时期自然只有服务端路由:

image.png

随着 ajax 的出现,开始了前后端分离的开发模式,Single Page Application 成为流行,后端只在前端请求时提供资源、不再负责拼接 html。所以服务端路由和前端路由并存: (前端服务器是什么:www.jianshu.com/p/d19af5649… ,前端服务器的路由应该也算是服务器的路由吧)

image.png

不过 SPA 对 SEO 不友好,所以又出现了新的服务器渲染:

image.png

(SSR 这部分有空可以去看一下 next.js)

react-router

react-router 实现前端路由

image.png

react-router v6 源码

源码分析文章,图和源码都来自于这几篇文章:

  1. juejin.cn/post/702179…
  2. juejin.cn/post/707514…
  3. juejin.cn/post/710086…

简单原理:

  1. react-router v6 依赖第三方库 history 管理会话历史,可以支持 hash、history 和 memory 模式,history 定义了一个事件中心,在触发 push、go 时,调用原生 history api 更改浏览器路由地址,并触发自定义事件;

  2. BrowserRouter(入口组件)监听 history 变化,并触发 setState,更新当前的 location 和 action(history 变化的来源:'POP' | 'PUSH' | 'REPLACE'),state 传入 Router 组件;

  3. Router 提供 NavigationContext 和 LocationContext 两个上下文; image.png

  4. Routes 作为 BrowserRouter 提供给 Router 的子组件,处于 Router 提供的上下文中。内部使用 useRoutes 渲染; image.png 其中 createRoutesFromChildren(children) 把 Route 子元素展开成为数组; image.png

  5. useRoutes 根据 location 上下文,先寻找到与当前 location 对应的路由对象(matchRoutes -> matches),再渲染为 react element(renderMatches);

  6. matchRoutes 先把所有路由规则打平为一个一维数组,并且给每个路由规则打分,按分数降序排序后,选择第一个匹配的路由规则作为最终采用的规则;

  7. matches 上记录了 pathname、element 和子路由规则等;

  8. renderMatches 从右至左遍历 matchs,将 Route 中提供的 element 进一步封装,并将遍历上一步得到的封装结果(overlay)作为 RouteContext 上下文提供给 elem;所以用户可以使用 overlay 元素,在父路由规则对应的元素中渲染子路由对应的元素; image.png image.png

  9. useNavigate 若收到 number 类型参数,调用 history.go 方法,若是字符串或者对象,处理后调用 history.replace 方法;

  10. link 元素是对原生 a 元素的封装,禁止了原生事件,所以点击跳转时也会触发 react-router 中的逻辑;