路由中那些困扰我的点

590 阅读6分钟

路由的前世今生

路由在我个人看来,其实就是来到某个位置后,告诉你接下来该往哪里走,十字路口的指示牌、商场中对洗手间电梯间的指引,都是生活中用到路由的场景。回到技术层面,路由最早是后端中的概念,访问某个地址,例如example.com/user,如果example.com对应的服务已经对/user进行了指引,那么就能达到它最终该去调用的方法。 前端的路由也类似,当我们访问某个路径时,如果对应的路径已经在路由上注册了,那么就能展示。但是在这里我一直有个疑问,例如同样是baidu.com/user这么一个路径,它到底是通过前端路由展示一个页面呢,还是通过后端路由去做其他的事呢?接下来我们先把问题简化,把前端路由统一理解成为了跳转页面,后端路由统一理解成为了调用接口,先把“这活到底是谁负责的”搞清楚。 ​

困扰1 -- 到底是谁在出力?

下载.png

这一节我们的目的是把一个路由到底是由前端还是后端处理搞清楚。现在主流的前端路由有两种方式,分别是用hash实现以及浏览器history实现,我们来逐个分析。

hash模式

原理是利用了url链接中的#锚点,跟在#后面的内容变化,会被hashChange时间监听到,且hash内容变化不会被后端所获取到。因此,当hash变化导致页面变化时,它就是一次纯粹的前端路由变化,不会涉及到后端路由。 image.png

history模式

原理是利用了浏览器提供的history对象来实现跳转,主要的API有以下几个:

  1. pushState
    1. 用于在历史中添加一条记录,但不会触发页面更新。即我们可以通过js,将页面的url变成我们想要的任何形式,且不会被后端感知到
    2. 调用方式是history.pushState(object, title, url)
      • object:一个对象,可以将该对象内容传递到新页面中。一般就是这个参数比较有意义
      • title:标题,一般不用
      • url:一般不用
  2. replaceState
    1. 用于将历史中最新的一条记录,更新为传入的新值,参数同pushState
  3. back
    1. 对应浏览器的回退功能
  4. forward
    1. 对应浏览器的前进功能
  5. go
    1. history.go(num) 跳转到相对当前页面的第num页
    2. history.go(-1)相当于history.back()
    3. history.go(1)相当于history.forward()
  6. popstate事件
    1. back/go/forward三个方法调用时,可以触发这个事件,我们可以根据最新的history值,渲染想要的页面

揭开真相

hash模式中,#后面的内容不会带到后端,所以当我们选择hash模式时,就是前端路由在帮我们做页面跳转。“我选它做话事人,它在为社团出力”。 ​

但是history模式的url对我们来讲就非常有迷惑性。当我们从example.com/list点击一个按钮,url变成了example.com/user,且页面也变成了/user对应的页面。从现象去分析的话,其实有不止一种方案可以实现:

  1. 利用前端路由的history模式,由js实现路由的跳转、页面的更新,并且把url也更新,这种情况后端不会感知到变化
  2. 利用后端路由直接拿example.com/user请求后端,后端返回对应的页面

显然第2种后端路由的方式是不如第1种前端路由的体验来的好,那我们该如何确认到底是采用那种方式呢? 其实很简单,在跳转之前,我们可以打开chrome开发者工具,在network tab中,如果是前端路由实现的,则不会有/user这个请求,而如果是后端路由实现的,则会有/user这个请求。 到此为止,我们就能够分辨出某次url变化,到底是由谁在帮助我们完成页面的更新了。 ​

困扰2 -- history模式需要后端配合是怎么回事

我们再来深挖一下history模式的使用场景,我们之前的例子中写到了,是点击按钮后,通过js,利用pushState或者replaceState达到更新url且更新页面的效果,这种情况下这个url不会请求到后端。但是,如果停留在这个url下,用户点击了浏览器的刷新按钮,则整个url都会请求到后端。在这种场景下,就需要后端路由能够认识这个url,否则就会出现404的情况。 大部分已经采用前后端分离的项目,后端或者nginx都会为当前域名开头的请求返回html,这也是history模式的一个小缺点(它需要后端配合)。返回html后,我们的js被加载,js代码中,路由相关的代码被执行,发现当前的url是/user,就会渲染出/user所对应的页面,最终完成页面的展示。

困扰3 -- 根据url渲染页面,这事是谁在管?

这个问题其实就是想知道当url发生变化时,是怎么做到根据url去展示页面的。这里我们借着react-router的history模式来分析一下这个过程。 有两个点先声明下,react-router中实现路由的最重要的两个组件是Router组件以及Route组件。

Router

Router组件其实是一个Context的Provider组件,它负责维护路由的信息,history、location等信息的改动,都在Router组件中维护并更新,利用“Provider值变化后,消费这个Provider的组件都会rerender”的特性,让Route组件能够感知到url的变化。

Route

Route组件中,根据配置的path属性和url是否匹配,来决定该渲染什么内容,简单理解就是path匹配了,渲染对应组件,不匹配那就什么都不渲染 ​

困扰4 -- 异步加载

有些小伙伴也会注意到,在项目生产环境中,会等到我们点进某个页面,才去请求对应的资源。其原理是在打包工具分片的基础上,在根入口文件中,存放着不同路由对应的不同文件名的配置,并会在url匹配时去获取。 image.png

总结

前端路由为页面的使用体验带来了很大的提升,真正让前端做到了除持久化数据之外,全由自己掌控的程度。 除了这次讲到的部分之外,还有需要知识点和路由有所交集。例如webpack的分片技术、react-router或vue-router中更加细节的路径匹配及组件渲染机制。下一次分享,就和大家一起探索下react-router的内部实现。 ​

参考文章

cloud.tencent.com/developer/a…

juejin.cn/post/694347…