一、要实现的功能
我们使用React
开发项目的时候,基本上是单页面应用,也就离不开路由。路由看似神秘,当我们简单的模拟一下它的核心功能后,发现也就这么回事儿。本文就详细的介绍一下react-router-dom
的 HashRouter
的核心实现逻辑。
本文主要实现的功能主要包含:
HashRouter
Route
Link
MenuLink
Switch
Redirect
二、 实现的逻辑
先不说代码是怎样写的,先上图,让大家看一下这个HashRouter
到底是个什么东东:
好吧,肯定有人会说这些圈圈又是什么东东呀,客官不要着急,待我慢慢解释:
HashRouter
是一个大的容器,它控制着他自己到底渲染成什么样子,那么它是通过什么控制的呢,看它的名字就能猜出来,那就是window.location.hash
。- 当
HashRouter
开始渲染的时候就会拿它自己身上的pathname
属性跟它肚子里的Route
的path
进行匹配,匹配上的话,就会渲染Route
的component
对应的组件。 - Link是怎样切换路由的呢,很简单,就是通过
this.props.history.push(path)
来改变HashRouter
中的pathname
属性,进而驱动Route
们 进行重新渲染,再次匹配我们的路由,最终实现路由的切换。
介绍了一下简单的逻辑,接下来我们就看一下具体是怎样实现的吧,如下图
HashRouter
是一个继承了React.Component
的类,这个类上的state
包括location
,监听着hash
的变化以驱动Route
组件的重新渲染,另外还有一个history
属性,可以切换页面的路由。- 本文要实现的功能中包括
Route
、Link
、MenuLink
、Switch
、Redirect
,其中Route
的是基础是核心,MenuLink
和某些有特定逻辑的渲染都是在Route
的基础上实现的。 Route
组件上可以接收三种变量,包括component
、render
、children
,其中render
、children
是都是函数,render
是根据特定的逻辑渲染元素,children
是用来渲染MenuLink
,这两个函数都接收当前路由的props
,函数的返回值是要渲染的元素。Switch
实现的逻辑是,返回children
中跟hash
匹配到的第一个“孩子
”。
三、开始撸代码
3.1 HashRouter
HashRouter
将window.loacation.hash
跟自己的state
挂钩,通过改变自己的state
驱动页面的重新渲染。
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class HashRouter extends Component {
constructor() {
super();
this.state = {
location: {
pathname: window.location.hash.slice(1) || '/', // 当前页面的hash值
state: {} //保存的状态
}
};
}
// 定义上下文的变量类型
static childContextTypes = {
location: PropTypes.object,
history: PropTypes.object
}
// 定义上下文的变量
getChildContext() {
return {
location: this.state.location,
history: {
push: (path) => { // 就是更新 window.hash值
if (typeof path === 'object') {
let {pathname, state} = path;
this.setState({
location: {
...this.state.location,
state // {from: '/profile'}
}
}, () => {
window.location.hash = pathname;
})
} else {
window.location.hash = path;
}
}
}
}
}
render() {
return this.props.children; // 渲染页面元素
}
componentDidMount() {
window.location.hash = window.location.hash.slice(1) || '/';
// 监听window的hash的变化,驱动页面的重新刷新
window.addEventListener('hashchange', () => {
this.setState({
location: {
...this.state.location,
pathname: window.location.hash.slice(1) || '/'
}
});
})
}
}
3.2 Route
Route
的渲染核心逻辑就是将自己的path
和当前页面的hash
进行匹配,匹配上了就渲染相应的元素,匹配不上就什么都不渲染。
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp'
export default class Route extends Component {
// 定义上下文context的类型
static contextTypes = {
location: PropTypes.object,
history: PropTypes.object
}
render() {
// 解构传入Route的props
let {path, component: Component, render, children} = this.props;
// 解构上下文的属性
let {location, history} = this.context;
let props = {
location,
history
};
// 将传入Route的path和当前的hash进行匹配
let keys = [];
let regexp = pathToRegexp(path, keys, {end: false});
keys = keys.map(key => key.name);
let result = location.pathname.match(regexp);
if (result) { // 匹配上了
let [url, ...values] = result;
props.match = {
path,
url,
params: keys.reduce((memo, key, index) => { // 获取匹配到的参数
memo[key] = values[index];
return memo;
}, {})
};
if (Component) { // 普通的Route
return <Component {...props} />;
} else if (render) { // 特定逻辑的渲染
return render(props);
} else if (children) { // MenuLink的渲染
return children(props);
} else {
return null;
}
} else { // 没有匹配上
if (children) { // MenuLink的渲染
return children(props);
} else {
return null;
}
}
}
}
3.3 Redirect
Redirect
就干了一件事,就是改变HashRouter
的state
,驱动重新渲染。
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class Redirect extends Component {
// 定义上下文context的Type
static contextTypes = {
history: PropTypes.object
}
componentDidMount() {
// 跳转到目标路由
this.context.history.push(this.props.to);
}
render() {
return null;
}
}
3.4 MenuLink
import React, {Component} from 'react';
import Route from "./Route";
import Link from './Link'
export default ({to, children}) => {
// 如果匹配到了,就给当前组件一个激活状态的className
return <Route path={to} children={props => (
<li className={props.match ? "active" : ""}>
<Link to={to}>{children}</Link>
</li>
)
}/>
}
3.5 Link
Link
就是渲染成一个a
标签,然后给一个点击事件,点击的时候更改HashRouter
的状态,驱动重新渲染。
import React, {Component} from 'react';
import PropTypes from 'prop-types';
export default class Link extends Component {
static contextTypes = {
history: PropTypes.object
}
render() {
return (
<a onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a>
)
}
}
3.6 Switch
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import pathToRegexp from 'path-to-regexp';
export default class Switch extends Component {
static contextTypes = {
location: PropTypes.object
}
render() {
let {pathname} = this.context.location;
let children = this.props.children;
for (let i = 0, l = children.length; i < l; i++) {
let child = children[i];
let path = child.props.path;
if (pathToRegexp(path, [], {end: false}).test(pathname)) {
// 将匹配到的第一个元素返回
return child;
}
}
return null
}
}
四、最后
好了,这几个功能介绍完了,你是否对HashRouter的原理有所了解了呢?
2022.6.3日(长期有效):打个广告,苏州华为终端BG面向社会招聘人才,Java /C C++ / Python / Javascript 。有兴趣来苏州的同学们 可以加我V 15850277051 ,走内推流程,有问必答!