react-router是配合react使用的路由控件,功能也很强大,而了解其设计原理开发者来说是有必要也是有帮助的。接下来探讨一下react-router的设计原理。
了解react-router原理,首先要知道几个概念:h5 history对象,js history库,react-router是基于它们实现的。
h5 history
可以很清楚的看到history对象的属性和方法:
其中使用 pushState(),replaceState()时,浏览器url改变,但是页面并不刷新
每次但我们触发history.back(), History.forward()或点击浏览器的前进后退按钮时,都会触发popstate事件。
每次触发history.back(), History.forward()或点击浏览器的前进后退按钮时,都会触发popstate事件。
所以如果使用history做为路由基础,就需要使用history.pushState和history.replaceState来改变url的值而不刷新页面,然后通过popstate事件执行页面的前进与后退
h5 demo 注意:该例子需要创建一个本地服务器启动应用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手动实现router</title>
</head>
<script>
/**
* create by lwz
* 设计思路
* 1,每个组件对应一个path
* 2, 每个组件在渲染后有一个特定的class,方便在路由切换后去操作目标组件
* 3,路由的嵌套,包装在children里
*/
</script>
<body>
<button onClick = "testPushState('/component1')">到component1</button>
<button onClick = "testPushState('/component2')">到component2</button>
<button>点击测试replaceState</button>
<a href="http://www.baidu.com">跳转页面</a>
</body>
<script class="components-define">
//放在这里定义,当执行移除后,component2变为未定义
// const component2 = document.createElement('div');
// component2.innerHTML = `<div class="bar2-component">
// 我是bar2-component
// <button onclick = "testPushState('/')">点击回首页</button>
// </div>`
const routes = [
{
path:'/component1',
component:() => {
const component1 = document.createElement('div');
component1.innerHTML = `<div class="bar1-component">
我是bar1-component
<button onclick = "testPushState('/')">点击回首页</button>
</div>`
return component1
}
},
{
path:'/component2',
component:() => {
const component2 = document.createElement('div');
component2.innerHTML = `<div class="bar2-component">
我是bar2-component
<button onclick = "testPushState('/')">点击回首页</button>
</div>`
return component2
}
}
]
function setView(path){
//...此处省略path判断和处理
//...
let component = null;
routes.forEach( (item, index) => {
let className = item.path.replace('/','');
if(item.path == path){
if(document.querySelector(`.${className}`)) return;
component = item.component();
console.log(component.innerHTML)
component.className = className
// component.innerHTML = component.innerHTML.replace(/class([\s]?|[\s]+})=("|')/i,function(match){
// return match.indexOf("'") > -1 ? "class = '" + className + " " : 'class = "' + className + ' '
// })
alert('添加')
console.log(component)
document.querySelector('body').append(component)
} else {
document.querySelector(`.${className}`) && document.querySelector(`.${className}`).remove()
}
})
}
</script>
<script>
function testPushState(path,component){
let stateObj = {
foo: "bar",
component:component
};
/**
* pushState() 需要三个参数:
* 一个状态对象,
* 一个标题 (目前被忽略),
* (可选的) 一个URL.
*/
history.pushState(stateObj, "page 2", path);
this.setView(path)
//document.querySelector('body').append(component1)
let currentState = history.state;
console.log(currentState)
}
window.addEventListener('popstate',(e)=>{
console.log('11212')
let pathname = window.location;
console.log(window.location)
//document.getElementById('body').innerHTML = window.location;
document.querySelector('body').style.backgroundColor = 'red'
})
console.log(history.state)
</script>
</html>
history 库
history是一个第三方js库,借鉴HTML5 history对象的理念,在其基础上又扩展了一些功能,用来管理历史记录,可以兼容不同的浏览器和不同的环境,根据不同的环境提供了三种不同的API。
HashHistory:针对老版本的浏览器,主要通过Hash实现。
BrowserHistory:针对较高版本的浏览器,主要通过H5的History实现。
MemoryHistory:主要通过内存中的历史记录实现。
History支持发布/订阅功能,当history发生改变的时候,可以自动触发订阅的函数,再对比一些两者api的异同。以下是history库的:
const history = {
length, // 属性,history中记录的state的数量
action, // 属性,当前导航的action类型
location, // 属性,location对象,封装了pathname、search和hash等属性
push, // 方法,导航到新的路由,并记录在history中
replace, // 方法,替换掉当前记录在history中的路由信息
go, // 方法,前进或后退n个记录
goBack, // 方法,后退
goForward, // 方法,前进
canGo, // 方法,是否能前进或后退n个记录
block, // 方法,跳转前让用户确定是否要跳转
listen // 方法,订阅history变更事件
};
react-router实现
设计思想:对History进行了一次封装,使能够识别将url的变化与componet渲染进行匹配
- 根据BrowserRouter等不同的API针对H5的history的重构
- 结构的构建,同时对history属性进行注册。
- 在Router的componentWillMount中注册history的事件回调。
- 在Redirect中进行路径的计算,调用history.push/history.replace等更新history信息。
- Route中根据计算的匹配结果,进行页面首次渲染/页面更新渲染处理。
具体实现代码:参见github
参考:https://blog.csdn.net/qq_36223144/article/details/83247008