经过前一段时间的学习,我们已经可以掌握React基础方面的代码实现逻辑。那么接下来的时间,让我们一起学习另一个模块:react 路由 吧!
React路由原理
一、 HashRouter
根据路径的不同,页面渲染不同的内容。
下面写个简单的小例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hash</title>
</head>
<body>
<div id="root"></div>
<ul>
<li>
<a href='#/a'> aaa </a>
</li>
<li>
<a href='#/b'> bbb </a>
</li>
</ul>
<script>
window.addEventListener('hashchange', () => {
console.log(window.location.hash);
let pathname = window.location.hash.slice(1) // #/a ==> /a
root.innerHTML = pathname
})
</script>
</body>
</html>
我们通过切换路由,去控制页面的内容。
二、 BrowserRouter
本质是利用h5的api实现路由切换。
思考运行以下代码,页面最终展示的结果是什么?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script>
// h5提供的history对象
var historyObj = window.history;
// 自定义属性:监听路径改变事件 表示将当前的状态变更了,弹出了
window.onpushstate = (event) => {
console.log(event.type,event.detail.state);
root.innerHTML = window.location.pathname;
}
// 调用go back forward会触发onpopstate
window.addEventListener('popstate', (event) => {
// state 用来存放数据的自定义属性
console.log(event.type,event.state);
root.innerHTML = window.location.pathname;
});
(function (historyObj) {
let oldPushState = history.pushState;//缓存原生的pushState
historyObj.pushState = function (state, title, pathname) {
let result = oldPushState.apply(history, arguments);
if (typeof window.onpushstate === 'function') {
window.onpushstate(new CustomEvent('pushstate',{detail:{pathname, state}}));
}
return result;
}
})(historyObj);
let oldHistoryLength = historyObj.length;
setTimeout(() => {
historyObj.pushState({ page: 1 }, { title: 'page1' }, '/page1');//page1
console.log(historyObj.length-oldHistoryLength);
}, 1000);
setTimeout(() => {
historyObj.pushState({ page: 2 }, { title: 'page2' }, '/page2');//page2
console.log(historyObj.length-oldHistoryLength);
}, 2000);
setTimeout(() => {
historyObj.pushState({ page: 3 }, { title: 'page3' }, '/page3');//page3
console.log(historyObj.length-oldHistoryLength);
}, 3000);
setTimeout(() => {
historyObj.back();//historyObj.go(-1);//page2
setTimeout(()=>console.log(historyObj.length-oldHistoryLength),100);
}, 4000);
setTimeout(() => {
historyObj.pushState({ page:4 }, { title: 'page4' }, '/page4');//page4
console.log(historyObj.length-oldHistoryLength);
}, 5000);
setTimeout(() => {
historyObj.go(1);
console.log(historyObj.length-oldHistoryLength);//page4
}, 6000);
</script>
</body>
</html>
运行过程及结果:
操作: push1 --> push2 --> push3 --> back --> push4 --> go1
页面展示: page1 --> page2 --> page3 --> page2 --> page4 --> page4
history栈长度: 1 2 3 3 3 3
1. history
html5规范给我们提供了一个history接口(js原生对象)。history的api:history.pushState() 放置/添加状态、history.replaceState()替换当前状态。history的事件:window.onpopstate。
可以理解为浏览器内部维护了一个history的 栈结构。
- 我们调用
history.pushState()方法添加一个条目,包含两个属性:pathname路径、state状态。栈中指针初始指向栈顶,每次调用pushState()方法,指针就会 指向新的条目。 - 调用
history.replaceState()方法,会把最后入栈的条目更新为本次新加的条目。 - 当操作浏览器前进、后退以及执行
history.forward()、history.back()、history.go等方法,都会触发onpopstate事件,并且指针指向当前的条目。也就是说,当栈指针发生移动,就会触发onpopstate事件。
1-1. pushState
history.pushState(stateObject, title, url),包括三个参数:
- 第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取
- 第二个参数是标题,目前浏览器并未实现
- 第三个参数则是设定的url
pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶
1-2. replaceState
- 该接口与pushState参数相同,含义也相同
- 唯一的区别在于
replaceState是替换浏览器历史堆栈的当前历史记录为设定的url - 需要注意的是
replaceState不会改动浏览器历史堆栈的当前指针
1-3. onpopstate
- 该事件是window的属性
- 该事件会在调用浏览器的前进、后退以及执行
history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针 - 在不改变 document 的前提下,一旦当前指针改变则会触发
onpopstate事件
当执行back、forward方法时,history栈的大小、长度等不会改变,即之前的条目不会弹出栈,改变的只是当前指针的位置。
若当前指针在history栈的中间位置(非栈顶),此时执行pushState会在指针当前的位置添加此条目,并成为新的栈顶。