react router原理探究(简版)

本文最后更新于:2 个月前

react router路由切换原理探究(简版)

在 Web 前端单页面应用 SPA(Single Page Application)中,路由是描述 URL 和 UI 之间的映射关系,这种映射是单向的,即 URL 的改变会引起 UI 更新,无需刷新页面

​ 根据上面这段话,可以得到,react想要实现路由页面切换需要完成3点:

  1. 引起URL的变化,并能够体现在浏览器的地址栏上
  2. URL的变化,不能引起整个页面的刷新
  3. URL的变化,需要引起局部UI(组件)的刷新

一、前置知识

前端路由有2种模式,hash 和 history

1、hash模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hash模式</title>
</head>

<body>
<a href="#/a">a页面</a>
<a href="#/b">b页面</a>
<div id="app"></div>

<script>
function render() {
document.querySelector('#app').innerHTML = window.location.hash
}
addEventListener('hashchange', render)
</script>
</body>

</html>

在浏览器中,打开改html文件,分别点击两个链接,可以看到视图中内容发生变化。同时浏览器不会重新发出资源请求。

hash模式的优缺点:

  • 优点:兼容性好
  • 缺点:路径在 #后面比较丑;在 SEO 中有不好的影响

2、history模式

history API 是 H5 提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL 地址而不重新发起请求

history对象一共有5个方法:

  • back(),返回上一页
  • forward(),进入浏览历史的下一页(如果有的话,即使没有也不报错)
  • go(),跳转到浏览历史的指定部分,go(1)等价于forward()go(-1)等价于back()
  • pushState(state, unused, [url]),向浏览器的会话历史栈增加一条记录。这个方法比较重要,下面会详细介绍。
  • replaceState(state, unused, [url]),修改当前历史记录条目的状态对象 or URL,与 pushState() 相似

popstate事件

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的 window 对象上触发。

点击浏览器的「后退」、「前进」按钮会触发,调用history对象的back()forward()go()这3个方法也会触发popstate事件;但调用pushState()replaceSate()不会触发popstate事件!

pushState()

1
pushState(state, unused, [url])
  • state是一个js对象,调用后换传递给history.state,通常传递一个空对象{}
  • unused,由于历史原因,该参数不再使用但不能忽略,通常传递null
  • [url][]表示可选,新历史条目的url
    1. 调用 pushState() 后,浏览器不会加载该URL,但它会在用户重启浏览器、会点击浏览器刷新按钮之后加载该URL;
    2. url可以是绝对路径;也可以是相对路径,相对路径将基于当前的URL进行解析;
    3. 传入的url必须与原先的url同源,否则将抛出异常;
    4. 如果url没有指定,相当于设置为当前的url;
    5. 该方法不会触发 popstate 事件。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>history模式</title>
</head>

<body>
<a href="javascript:toA()">a页面</a>
<a href="javascript:toB()">b页面</a>
<a href="#/c">c页面</a>

<div id="app"></div>
<script>
function render() {
console.log('触发渲染');
document.querySelector('#app').innerHTML = location.pathname
}
function toA() {
console.log('点击a');
history.pushState({}, null, '/a')
render()
}
function toB() {
console.log('点击b');
history.pushState({}, null, '/b')
render()
}
addEventListener('popstate', render);
</script>
</body>

</html>

(ps:需要使用light-server模块构造出一个本地服务器来打开html,light-server -s . --historyindex '/history.html' --port 3000。如果本地直接打开html,url无法按照预期效果生效,如下)

image-20240121233549551

实测发现,<a href="#/c">c页面</a>这种方式也能触发 popstate 事件

优缺点

  • 优点:url整体美观规范
  • 缺点:兼容性比hash差;需要服务端支持SPA的History API模式

扩展:light-server的作用

官方文档:light-server - npm (npmjs.com)

在上面history的演示中,对light-server模块最核心使用的功能是History API模式。由参数--historyindex指定。

1
2
3
4
5
Usage: light-server [options]

Options:
-s, --serve <directory> serve the directory as static http
--historyindex <historyindex> 404 fallback index path, used by SPA development

其实 History API 模式的原理很简单!它所做的就是在服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。

二、如何引起URL变化

如【前置知识】介绍,通过改变hash、调用history对象的方法这两种方式,都可以改变浏览器URL框中的 URL,而同时不会触发页面的重新请求。

hash方式

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<a href="#/a">a页面</a>
<a href="#/b">b页面</a>
<div id="app"></div>

<script>
function render() {
document.querySelector('#app').innerHTML = window.location.hash
}
addEventListener('hashchange', render)
</script>
</body>

history API方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<a href="javascript:toA()">a页面</a>
<a href="javascript:toB()">b页面</a>
<a href="#/c">c页面</a>

<div id="app"></div>
<script>
function render() {
document.querySelector('#app').innerHTML = location.pathname
}
function toA() {
history.pushState({}, null, '/a')
render()
}
function toB() {
history.pushState({}, null, '/b')
render()
}
addEventListener('popstate', render);
</script>
</body>

三、URL的变化如何触发UI更新

正如前面所言,不管是hash的变化,还是调用history API,浏览器都不会重新发出资源请求。

但同时,我们可以注意到通常SPA的路由切换之后,浏览器都会重新发出一堆对.js.css等文件的请求,那么这些请求又是由哪里操作发出的呢?

另外,页面UI是如何随着 URL 的变化同步刷新的呢?


react router原理探究(简版)
http://timegogo.top/2024/01/14/React/react-router原理探究/
作者
丘智聪
发布于
2024年1月14日
更新于
2024年1月22日
许可协议