react-router 秉承一切皆组件,因此实现的核心就是BrowserRouter、Route、Link
一、实现BrowserRouter、Route、Link
BrowserRouter: 历史纪录管理对象history初始化及向下传递,location变更监听
1、创建:my-react-router-dom.js
import React, { Component, createContext, useContext } from 'react'
import { createBrowserHistory } from "history"
import matchPath from './matchpath'
const RouterContext = createContext();
const RouterProvider = RouterContext.Provider;
const RouterConsumer = RouterContext.Consumer;
//实现BroserRouter
export class BrowserRouter extends Component {
constructor(props) {
super(props)
//往下传递,在Link 中使用history
this.history = createBrowserHistory(this.props);
//location 获取最顶层的路径,保留下来往下传递,方便判断路由对应要显示的页面
this.state = {
location: this.history.location
}
//history.listen 因为location会变化,所以监听更新它的值
this.unlisten = this.history.listen(location => {
this.setState({ location })
})
}
//卸载监听
componentWillUnmount() {
if (this.unlisten) {
this.unlisten();
}
}
render() {
return (
<div>
<RouterProvider children={this.props.children || null}
value={{
history: this.history,
location: this.state.location
}}
/>
</div>
)
}
}
// 实现Route
export function Route(props) {
//useContext function组件获取上层组件传过来的值
const ctx = useContext(RouterContext);
//获取到 BrowserRouter 传来的location
const { location } = ctx;
const { path, component, children, render } = props;
const match = matchPath(location.pathname, props);
const matchCurrent = match && match.isExact;
console.log('matchCurrent', matchCurrent);
const cmpProps = { ...ctx, match };
if (matchCurrent && typeof children === "function") {
return children(cmpProps);
}
return (
<>
{typeof children === 'function' && children(cmpProps)}
{
matchCurrent && component
? React.createElement(component, cmpProps)
: null
}
{matchCurrent && !component && render && render(cmpProps)}
</>
)
}
//实现Link 跳转链接,处理点击事件
export class Link extends Component {
handleClick = (e, history) => {
e.preventDefault();
history.push(this.props.to)
}
render() {
const { to, children } = this.props;
return (
<RouterConsumer>
{ctx => (
<a href={to} onClick={e => this.handleClick(e, ctx.history)}>
{children}
</a>
)}
</RouterConsumer>
)
}
}
2、matchpath实现
Router组件依赖于matchpath
matchpath.js
import { pathToRegexp } from "path-to-regexp";
const cache = {};
const cacheLimit = 10000;
let cacheCount = 0;
function compilePath(path, options) {
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
if (pathCache[path]) return pathCache[path];
const keys = [];
const regexp = pathToRegexp(path, keys, options);
const result = { regexp, keys };
if (cacheCount < cacheLimit) {
pathCache[path] = result;
cacheCount++;
}
return result;
}
/**
* Public API for matching a URL pathname to a path.
*/
function matchPath(pathname, options = {}) {
if (typeof options === "string") options = { path: options };
const { path, exact = false, strict = false, sensitive = false } = options;
const paths = [].concat(path);
return paths.reduce((matched, path) => {
if (!path) return null;
if (matched) return matched;
const { regexp, keys } = compilePath(path, {
end: exact,
strict,
sensitive,
});
const match = regexp.exec(pathname);
if (!match) return null;
const [url, ...values] = match;
const isExact = pathname === url;
if (exact && !isExact) return null;
return {
path, // the path used to match
url: path === "/" && url === "" ? "/" : url, // the matched portion of the URL
isExact, // whether or not we matched exactly
params: keys.reduce((memo, key, index) => {
memo[key.name] = values[index];
return memo;
}, {}),
};
}, null);
}
export default matchPath;
3、测试
常见MyRouterPage.js
import React, { Component } from 'react'
import { BrowserRouter, Link, Route } from "../my-react-router-dom"
export function HomPage() {
return <div>HomPage</div>
}
export function UserPage() {
return <div>UserPage</div>
}
export default class MyRouterPage extends Component {
render() {
return (
<div>
<h1>MyRouterPage</h1>
{/*
//BrowserRouter 可以往下传递一些值:如 history,location...
BrowserRouter对数据做统一的管理,如果哪些数据改变了,就在顶层做数据的改变
,对它下面所有的组件进行派发
*/}
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/user">用户中心</Link>
</nav>
<Route exact path="/" component={HomPage} children={() => <div>children page</div>} />
<Route path="/user"
// component={UserPage}
render={() => <div>render page</div>} />
</BrowserRouter>
</div>
)
}
}
完毕!