react-router原理探索
history
history是浏览器提供的api,window.history指向History对象,表示当前的浏览历史。提供的api如下:
history.pushState(state,title,url)// 往路由加入要给堆栈,并且跳转,不刷新页面
history.replaceState(state,title,url)// 替换当前路由,不刷新页面。
history.go()// 后退或前进几次
history.forward()// 前进
history.back()// 返回
history不怕刷新,不怕前进和后退。怕刷新。因为刷新会实实在在请求后端服务器,如果服务器没有对应的资源返回会得到404。
hash
hash是为前端spa单页面准备的,地址栏后面的#符号,www.aaa.com/aaa/#/hello,#/hello 就是hash值。他的改变不会重新加载页面,对后端没有影响。提供了hashChange监听函数,
<!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>history</title>
</head>
<body>
<div>
<a href="#/111">111</a>
<a href="#/222">222</a>
<a href="#/333">333</a>
</div>
<script>
window.addEventListener('hashchange',(e)=>{
console.log('e',e);
console.log('oldURL', e.oldURL);
console.log('newRUL', e.newURL);
})
</script>
</body>
</html>
history和hash对比
history更符合我们常见的路由,但他需要服务器的支持,也便于seo优化。怕刷新
hash路由后面多了#号,会丑一点,他的路由控制完全交给前端,不需要后端介入。对于前端spa单页面应用。不便于seo优化。
react-router-demo
案例代码
用到了两种路由,切换即可,Link、Route
import React from "react";
import {
HashRouter as Router,
Route,
Link
} from "../router-demo/react-router-dom";
import About from '../pages/About'
import Home from '../pages/Home'
import Users from '../pages/Users'
const RouterProvider=()=>{
return <Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/" exact>
<Home />
</Route>
</div>
</Router>
}
export default RouterProvider;
react-router-dom----BrowserRouter和HashRouter
这两个都是基于history这个库去创建的基于window.history和hash的路由,代码相对简单
import React from 'react';
import {createHashHistory} from 'history';
import { Router } from '../react-router';
export default class BrowserRouter extends React.Component{
// 创建hash路由实例,包含push等操作。
history=createHashHistory();
render(){
// 返回router,利用react的context做深层组件的值传递。
return <Router history={this.history} children={this.props.children} />;
}
}
react-router—Router
import React from 'react';
// 这个RouterContext就是创建routerContet而已。
import RouterContext from './RouterContext';
class Router extends React.Component {
constructor(props) {
super(props);
this.state = {
location: props?.history.location
};
// 重点是在这里的,当我们使用的history的库,他为我们封装好的监听路由改变的方法,监听到改变,把当前的location接收到,通过context传递。
props.history.listen(location=>{
this.setState({
location
})
})
}
static computeRootMatch(pathname) {
return {
path: "/",
url: "/",
params: {},
isExact: pathname === "/"
};
}
render() {
// 使用react的context多组件共享location,
return <RouterContext.Provider
children = {
this.props.children || null
}
value = {
{
history: this.props.history,
// 这里传递。
location:this.state.location,
match:Router.computeRootMatch(this.state.location?.pathname),
}
}
>
</RouterContext.Provider>
}
}
export default Router;
react-router—Route
import React from 'react';
import RouterContext from './RouterContext';
import matchPath from './matchPath';
class Route extends React.Component{
render(){
return <RouterContext.Consumer>
{context=>{
// 这要靠match,去控制是否显示对应的路由
const match=matchPath(context.location,this.props);
return match?this.props.children:null;
}}
</RouterContext.Consumer>
}
}
export default Route;
react-router—matchPath
const matchPath=(pathname,options={})=>{
if(pathname.pathname===options.path) return true;
return false;
}
export default matchPath;
这一整个逻辑下来,简易版本的react-router就已经形成了。能够实现路由的跳转相关操作。
写到最后
总结,react-router不算复杂,他的思路是根据浏览器自身的history和hash路由,监听路由的变化,利用context传递当前location和Route进行比较,控制渲染。
简易版本实现的地址:https://github.com/lqy0101/fe-code-analysis/tree/main/example/react/react-router-demo