import HashRouter from './HashRouter';
import Route from './Route';
import Link from './Link';
Import Switch from './Switch';
import Redirect from './Redirect';
import NavLink from './NavLink';
import WithRouter from './WithRouter';
import Prompt from './Prompt';
import BrowserRouter from './BrowserRouter';
export {
HashRouter,
Route,
Link,
Swicth,
Redirect,
NavLink,
WithRouter,
Prompt,
BrowserRouter
}
复制代码
HashRouter
事实上HashRouter只是个容器,并不具备DOM结构,他的职责就是渲染他的子组件,和想下层组件传递数据
exp: location
。css
<!--建立并导出一个HashRouter文件-->
import React from 'react';
import RouterContext from './RouterContext';
export default class HashRouter extends React.Component {
state = {
location : {
// 去掉#号,hash是带有#
pathname: window.location.hash.slice(1)
}
}
componentDidMount(){
// 监听haschange事件,当以触发hashchange事件就是去改变当前的状态以后再同步hash的值
window.addEventListener('hashchange', () = > {
this.setState({
...this.state.location,
pathname: window.location.hash.slice(1) || '/',
state: this.locationState
})
});
window.location.hash = window.location.hash || '/';
}
render() {
let self = this;
let history = {
location: this.state.location,
push(to) {
if(typeof to === 'object') { // 有可能用户传递的是个包含路径和状态的对象
let {pathname, state} = to;
that.locationState = state;
window.location.hash = pathname;
} else { //传递的字符串
window.location.hash = to
}
}
block(prompt){
history.prompt = prompt
}
unblock() {
history.prompt = null;
}
}
let routerValue = {
location: that.state.location,
history
}
return (
<RouterContext.Provider value= {routerValue}> {this.props.chilren} </RouterContext.Provider> ) } } 复制代码
这里咱们会实现Route路由组件,他不少时候表明的是一条路由规则。hashRouter中的state、location、pathname是经过上下文传递出去的。vue
import React from 'react';
import RouterContext from './RouterContext';
import pathToRegexp from ' path-to-regexp';
export default class Route extends React.Component {
static contextType = RouterContext; // this.context.location.pathname
// component是route组件里面传过来的那个component属性值也就是该条路由所对应的组件,这里我将它重命名为RouteComponent。
// path是该路由组件上传过来的path路径用来作匹配的
// excat 表明的是该条路由上的路径是否是精确匹配和`path-to-regexp`的参数对应。
// 组件的渲染有三种方式分别是传入一个render函数进行渲染、传入一个children并且改children属性是一个函数执行便可实现渲染、最后就是直接按照匹配的组件进行渲染。
render() {
let { path='/', component: RouteComponent, excat= false, render, children } = this.props;
let path = typeof path === 'object' ? path.pathname : path;
let pathname = this.context.location.pathname;
let paramNames = [];
let regexp = pathToRegexp(path, paramNames, {end: exact});
paramNames = paramNames.map(p=>p.name);
let matched = pathname.match(regexp);
let routeProps = {
location: this.context.location,
history: this.context.history
};
if(matched) {
let [url, ...values] = matched;
let params = values.reduce((memo, cur, index) => {
memo[paramNames[index]] = cur;
return memo;
}, {})
let match - {
url,
path,
isExact: pathname === url,
params
}
routerProps.match = match;
if(RouteComponent) {
return <RouteCompoponent {...routerProps}/> } else if(render) { return render(routerProps); } else if(children) { children(routerProps); } else { return null; } } else { if(children) { return children(routerProps); } else { return null; } } } } // match 属性的存在与否很大程度上和这个组件是不是路由渲染出来的正相关 // isExact 表示的是是否为精确匹配,同时也是表明了pathname和url是否彻底相同 复制代码
// 不少人会问上面那个为何有个文件不知道在哪,那么接下来我就来实现一下RouterContext文件
// 这个其实贼简单,不信你看看就知道了
import React from 'react';
export default const context = React.createContext();
// 要不要太简单了一点
复制代码
<!--这里面我以为仍是用函数组件来实现比较好,毕竟都已经functional programming-->
import React from 'react';
import RouterContext from 'RouterContext';
//注意千万不要写href而且赋值为具体路径,由于他会绕过路由的拦截走a标签的属性
export default function Link(props) {
return (
<RouterContext.Consumer> { routerValue => { <a {...props} onClick ={ () => { routerValue.history.push(props.to) } } > { prpos.children } </a> } } </RouterContext.Consumer> ) } 复制代码
vue
的router-view
很像。import React, {useContext} from 'react';
import RouterContext from 'RouterContext';
import pathToRegexp from 'path-to-regexp';
// 这里我又引入了一个新东西useContext是的他就是传说中的hooks之一了。
// useContext是获取上下文对象的第三种方式
// static contextType (类中使用)
// Consumer(函数中使用)
// 还能够ReactHooks useContext获取上下文对像
export default function (props) {
let routerContext = useContext(RouterContext);
let children = props.children;
children = Array.isArray(children) ? children : [children];
let pathname = routerContext.location.pathname;// 从上下文中取出当前的路径
for(let i = 0; i < children.length; i++) {
// 千万记住这里的child是React元素或者虚拟DOM并非什么组件
// 至关因而`React.createElement(Route,{exact,path,component})`的结果,
// `{type:Route,props:{exact,path,component}}`
let child = children[i];
let { path = '/', component, exact = false} = child.props;
<!--这里面就用到了路由处理的正则库不明白的能够看看我以前的一篇介绍path-to-regexp的文章--> let regexp = pathToRegexp(path, [], {end: exact}); <!--用当前的路径去匹配--> let matched = pathname.match(regexp); <!--匹配到了直接渲染子组件--> if(matched) { return child; } } <!--没有匹配的子组件则return null--> return null } 复制代码
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
let routerContext = useContext(RouterContext);
if(!props.from || props.from === routerContext.location.pathname) {
routerContext.history.push(props.to);
}
return null;
}
复制代码
.acitve {
color: #425658;
background: #eeeddd;
}
复制代码
import React from 'react';
import './NavLink.css'
import { Route, Redirect, Link } from './index';
export default function (props) {
let { to, exact, children } = props;
return (
<Route path={to} exact={exacr} children={ routerProps => { <Link className={ routerProps.match? 'active' : '' to={to} }> {children} </Link> } }> </Route>
)
}
复制代码
import React, {useContext} from 'react';
import RouterContext from './RouterContext';
export default function (props) {
let routerContext = useContext(RouterContext);
let {bool, msg} = props;
if(bool) {
routerContext.history.block(msg);
} else {
routerContext.history.unblock()
}
return null
}
复制代码
<!--1、withContext.js-->
import React from 'react';
import { Route } './index';
export default function (OldComponent) {
reutnr props => {
<Route>
render={
routerProps =><OldComponent {...props} {...routerProps}/> } </Route>
}
}
export default function (OldComponent) {
return (
<RouterContext.Consumer> { contextValue => ( <div> <OldComponent/> </div> ) } </RouterContext.Consumer> ) } 复制代码
import React, { useState, useEffect } from 'react';
import RouterContext from "./RouterContext.js";
export default function BrowserRouter(props) {
let [currentState, setCurrentState] = useState({ location: { pathname: window.location.pathname } });
useEffect(() => {
window.onpushstate = (state, pathname) => {
setCurrentState({
location: {
...currentState.location,
pathname,
state
}
});
}
window.onpopstate = (event) => {
setCurrentState({
location: {
...currentState.location,
pathname: window.location.pathname,
state: event.state
}
});
}
}, []);
const globalHistory = window.history;
let history = {
location: currentState.location,
push(to) {
if (history.prompt) {
let target = typeof to === 'string' ? { pathname: to } : to;
let yes = window.confirm(history.prompt(target));
if (!yes) return;
}
if (typeof to === 'object') {//传的是一个对象 {pathname,state}
let { pathname, state } = to;
globalHistory.pushState(state, null, pathname);
} else {//就是个字符串
globalHistory.pushState(null, null, to);
}
},
block(prompt) {
history.prompt = prompt;
},
unblock() {
history.prompt = null;
}
}
let routerValue = {
location: currentState.location,
history
}
return (
<RouterContext.Provider value={routerValue}> {props.children} </RouterContext.Provider> ) } 复制代码